cwist
Modern C web framework with libttak runtime
Loading...
Searching...
No Matches
openapi.c
Go to the documentation of this file.
1
13#define _POSIX_C_SOURCE 200809L
14#include <cwist/openapi.h>
16#include <cjson/cJSON.h>
17#include <pthread.h>
18#include <string.h>
19#include <strings.h>
20
21#include "routes_internal.h"
22
23typedef struct openapi_note {
25 char *path;
26 char *summary;
27 char *tags;
28 struct openapi_note *next;
30
31typedef struct openapi_doc {
32 cwist_app *app;
33 char *json_path;
34 char *title;
35 char *version;
36 char *description;
37 char *server_url;
38 openapi_note *notes;
39 struct openapi_doc *next;
41
42static openapi_doc *g_docs;
44
45static char *dup_or_null(const char *s) {
46 return s ? cwist_strdup(s) : NULL;
47}
48
49static cwist_http_method_t method_from_string(const char *method) {
50 static const cwist_http_method_t all[] = {
53 };
54 for (size_t i = 0; i < sizeof(all) / sizeof(all[0]); i++) {
55 if (strcasecmp(method, cwist_http_method_to_string(all[i])) == 0) return all[i];
56 }
57 return CWIST_HTTP_UNKNOWN;
58}
59
60/* Caller holds g_docs_mu. */
61static openapi_doc *find_doc(const cwist_app *app) {
62 for (openapi_doc *d = g_docs; d; d = d->next) {
63 if (d->app == app) return d;
64 }
65 return NULL;
66}
67
68static const openapi_note *find_note(const openapi_doc *doc, cwist_http_method_t method,
69 const char *path) {
70 for (const openapi_note *n = doc->notes; n; n = n->next) {
71 if (n->method == method && strcmp(n->path, path) == 0) return n;
72 }
73 return NULL;
74}
75
76static void doc_destroy(void *ctx) {
77 openapi_doc *doc = ctx;
78 pthread_mutex_lock(&g_docs_mu);
79 for (openapi_doc **p = &g_docs; *p; p = &(*p)->next) {
80 if (*p == doc) {
81 *p = doc->next;
82 break;
83 }
84 }
85 pthread_mutex_unlock(&g_docs_mu);
86
87 while (doc->notes) {
88 openapi_note *n = doc->notes;
89 doc->notes = n->next;
90 cwist_free(n->path);
91 cwist_free(n->summary);
92 cwist_free(n->tags);
94 }
95 cwist_free(doc->json_path);
96 cwist_free(doc->title);
97 cwist_free(doc->version);
98 cwist_free(doc->description);
99 cwist_free(doc->server_url);
100 cwist_free(doc);
101}
102
103/* "/users/:id" -> "/users/{id}", with one required path parameter per segment. */
104static char *openapi_path(const char *route, cJSON *params) {
105 size_t len = strlen(route);
106 char *out = cwist_alloc(len * 2 + 1);
107 if (!out) return NULL;
108 char *w = out;
109 for (const char *r = route; *r;) {
110 if (*r != ':' || (r != route && r[-1] != '/')) {
111 *w++ = *r++;
112 continue;
113 }
114 const char *name = ++r;
115 while (*r && *r != '/') r++;
116 *w++ = '{';
117 memcpy(w, name, (size_t)(r - name));
118 w += r - name;
119 *w++ = '}';
120
121 char *pname = cwist_strndup(name, (size_t)(r - name));
123 cJSON_AddStringToObject(p, "name", pname ? pname : "");
125 cJSON_AddStringToObject(p, "in", "path");
126 cJSON_AddBoolToObject(p, "required", 1);
127 cJSON_AddStringToObject(cJSON_AddObjectToObject(p, "schema"), "type", "string");
129 }
130 *w = '\0';
131 return out;
132}
133
134typedef struct {
135 const openapi_doc *doc;
136 cJSON *paths;
137} build_ctx;
138
139static void add_operation(void *ctx, cwist_http_method_t method, const char *route) {
140 build_ctx *b = ctx;
141 if (method == CWIST_HTTP_CONNECT || method == CWIST_HTTP_UNKNOWN) return;
142 if (method == CWIST_HTTP_GET && strcmp(route, b->doc->json_path) == 0) return;
143
145 char *path = openapi_path(route, params);
146 if (!path) {
148 return;
149 }
151 if (!item) item = cJSON_AddObjectToObject(b->paths, path);
152 cwist_free(path);
153
154 char verb[16];
155 snprintf(verb, sizeof(verb), "%s", cwist_http_method_to_string(method));
156 for (char *c = verb; *c; c++) *c = (char)(*c | 0x20);
157
159 const openapi_note *note = find_note(b->doc, method, route);
160 if (note && note->summary) cJSON_AddStringToObject(op, "summary", note->summary);
161 if (note && note->tags) {
162 cJSON *tags = cJSON_AddArrayToObject(op, "tags");
163 char *list = cwist_strdup(note->tags);
164 char *save = NULL;
165 for (char *t = list ? strtok_r(list, ",", &save) : NULL; t; t = strtok_r(NULL, ",", &save)) {
166 while (*t == ' ') t++;
168 }
170 }
171 if (cJSON_GetArraySize(params) > 0)
172 cJSON_AddItemToObject(op, "parameters", params);
173 else
175 cJSON *ok = cJSON_AddObjectToObject(cJSON_AddObjectToObject(op, "responses"), "200");
176 cJSON_AddStringToObject(ok, "description", "OK");
177}
178
179static void openapi_handler(void *ctx, cwist_http_request *req, cwist_http_response *res) {
180 (void)req;
181 const openapi_doc *doc = ctx;
182
184 cJSON_AddStringToObject(root, "openapi", "3.1.0");
186 cJSON_AddStringToObject(info, "title", doc->title ? doc->title : "CWIST API");
187 cJSON_AddStringToObject(info, "version", doc->version ? doc->version : "1.0.0");
188 if (doc->description) cJSON_AddStringToObject(info, "description", doc->description);
190 cJSON_AddStringToObject(server, "url", doc->server_url ? doc->server_url : "/");
192
193 build_ctx b = {.doc = doc, .paths = cJSON_AddObjectToObject(root, "paths")};
194 pthread_mutex_lock(&g_docs_mu);
195 cwist_app_foreach_route(doc->app, add_operation, &b);
196 pthread_mutex_unlock(&g_docs_mu);
197
198 char *json = cJSON_PrintUnformatted(root);
200 if (!json) {
202 return;
203 }
204 cwist_sstring_assign(res->body, json);
205 cJSON_free(json);
207 cwist_http_header_add(&res->headers, "Content-Type", "application/json");
208}
209
210void cwist_app_enable_openapi(cwist_app *app, const char *json_path,
211 const cwist_openapi_meta_t *meta) {
212 if (!app) return;
213 pthread_mutex_lock(&g_docs_mu);
214 bool enabled = find_doc(app) != NULL;
215 pthread_mutex_unlock(&g_docs_mu);
216 if (enabled) return;
217
218 openapi_doc *doc = cwist_alloc(sizeof(*doc));
219 if (!doc) return;
220 doc->app = app;
221 doc->json_path = cwist_strdup(json_path ? json_path : "/openapi.json");
222 if (meta) {
223 doc->title = dup_or_null(meta->title);
224 doc->version = dup_or_null(meta->version);
225 doc->description = dup_or_null(meta->description);
226 doc->server_url = dup_or_null(meta->server_url);
227 }
228 if (!doc->json_path) {
229 cwist_free(doc);
230 return;
231 }
232
233 pthread_mutex_lock(&g_docs_mu);
234 doc->next = g_docs;
235 g_docs = doc;
236 pthread_mutex_unlock(&g_docs_mu);
237
238 cwist_error_t err = cwist_app_get_ex(app, doc->json_path, openapi_handler, doc, doc_destroy);
239 if (!cwist_error_is_ok(&err)) doc_destroy(doc);
240}
241
242void cwist_openapi_annotate(cwist_app *app, const char *method, const char *path,
243 const char *summary, const char *tags) {
244 if (!app || !method || !path) return;
245 cwist_http_method_t m = method_from_string(method);
246 if (m == CWIST_HTTP_UNKNOWN) return;
247
248 pthread_mutex_lock(&g_docs_mu);
249 openapi_doc *doc = find_doc(app);
250 openapi_note *note = doc ? (openapi_note *)find_note(doc, m, path) : NULL;
251 if (doc && !note && (note = cwist_alloc(sizeof(*note)))) {
252 note->method = m;
253 note->path = cwist_strdup(path);
254 if (!note->path) {
256 note = NULL;
257 } else {
258 note->next = doc->notes;
259 doc->notes = note;
260 }
261 }
262 if (note) {
263 cwist_free(note->summary);
264 cwist_free(note->tags);
265 note->summary = dup_or_null(summary);
266 note->tags = dup_or_null(tags);
267 }
268 pthread_mutex_unlock(&g_docs_mu);
269}
Memory allocation wrappers backed by libttak.
void cwist_free(void *ptr)
Free memory obtained via cwist_alloc/cwist_strdup/etc.
Definition alloc.c:52
char * cwist_strdup(const char *src)
Duplicate a string using libttak-backed storage.
Definition alloc.c:32
char * cwist_strndup(const char *src, size_t n)
Duplicate up to n bytes of a string (libttak-backed).
Definition alloc.c:41
void * cwist_alloc(size_t size)
Allocate zeroed memory tracked by libttak.
Definition alloc.c:17
cwist_error_t cwist_sstring_assign(cwist_sstring *str, const char *data)
Assign a C string.
Definition sstring.c:504
@ CWIST_HTTP_INTERNAL_ERROR
Definition http.h:35
@ CWIST_HTTP_OK
Definition http.h:28
cwist_http_method_t
Definition http.h:16
@ CWIST_HTTP_OPTIONS
Definition http.h:23
@ CWIST_HTTP_HEAD
Definition http.h:22
@ CWIST_HTTP_PUT
Definition http.h:19
@ CWIST_HTTP_PATCH
Definition http.h:21
@ CWIST_HTTP_UNKNOWN
Definition http.h:24
@ CWIST_HTTP_POST
Definition http.h:18
@ CWIST_HTTP_GET
Definition http.h:17
@ CWIST_HTTP_DELETE
Definition http.h:20
cwist_error_t cwist_http_header_add(cwist_http_header_node **head, const char *key, const char *value)
Add a header to the list.
Definition http.c:1861
const char * cwist_http_method_to_string(cwist_http_method_t method)
Convert method enum to string.
Definition http.c:1554
@ CWIST_HTTP_CONNECT
Definition http.h:45
struct openapi_doc openapi_doc
void cwist_app_enable_openapi(cwist_app *app, const char *json_path, const cwist_openapi_meta_t *meta)
Serve the app's OpenAPI 3.1 document.
Definition openapi.c:210
struct openapi_note openapi_note
void cwist_openapi_annotate(cwist_app *app, const char *method, const char *path, const char *summary, const char *tags)
Attach a summary and tags to one operation of the document.
Definition openapi.c:242
OpenAPI 3.1 generation — source annotations and runtime serving.
size_t cap
Definition otel.c:56
Read-only walk of an app's route table for in-tree consumers (the runtime OpenAPI generator in openap...
void cwist_app_foreach_route(cwist_app *app, cwist_route_visit_fn visit, void *ctx)
Main Application Context.
Definition app.h:155
Integrated error structure used throughout the system.
Definition cwist_err.h:72
HTTP Response Object. Supports standard string body or Zero-Copy pointer body.
Definition http.h:82
cwist_sstring * body
Definition http.h:87
cwist_http_status_t status_code
Definition http.h:84
cwist_http_header_node * headers
Definition http.h:86
Per-application metadata for the generated OpenAPI document.
Definition openapi.h:44
cwist_error_t cwist_app_get_ex(cwist_app *app, const char *path, cwist_handler_ex_func handler, void *user_ctx, cwist_handler_ctx_destroy_func destroy)
Registers a GET route whose handler receives a user context.
Definition app.c:2180