11typedef struct graphql_field {
15 struct graphql_field *next;
18struct cwist_graphql_schema {
40static bool gql_name_char(
char c,
bool first) {
42 return (
c ==
'_' ||
isalnum((
unsigned char)
c));
51static bool gql_name_valid(
const char *s) {
52 if (!s || !gql_name_char(*s,
true))
return false;
54 if (!gql_name_char(*s,
false))
return false;
65static void gql_add_error(
cJSON *errors,
const char *message) {
82 schema->queries =
NULL;
83 schema->mutations =
NULL;
107 free_fields(schema->queries);
108 free_fields(schema->mutations);
126 if (!
head || !gql_name_valid(field) || !
resolver)
return false;
128 if (
strcmp(
f->name, field) == 0) {
135 if (!
f)
return false;
159 if (!schema)
return false;
160 return add_field(&schema->queries, field,
resolver, ctx);
174 if (!schema)
return false;
175 return add_field(&schema->mutations, field,
resolver, ctx);
186 if (
strcmp(
f->name, name) == 0)
return f;
196static const char *skip_ws_comments(
const char *
p) {
198 if (
isspace((
unsigned char)*
p) || *
p ==
',') {
200 }
else if (*
p ==
'#') {
201 while (*
p && *
p !=
'\n' && *
p !=
'\r')
p++;
224 p = skip_ws_comments(
p);
225 if (*
p !=
'(')
return p;
230 gql_add_error(errors,
"Internal memory error parsing arguments");
235 p = skip_ws_comments(
p);
236 if (*
p ==
')' || *
p ==
'\0') {
243 if (!gql_name_char(*
p,
true)) {
244 gql_add_error(errors,
"Invalid argument name");
248 while (*
p && gql_name_char(*
p,
false))
p++;
255 p = skip_ws_comments(
p);
257 gql_add_error(errors,
"Expected ':' after argument name");
262 p = skip_ws_comments(
p);
269 while (*
p && *
p !=
'"') {
270 if (*
p ==
'\\' && *(
p + 1))
276 gql_add_error(errors,
"Unterminated string in argument");
289 }
else if (*
p ==
'$') {
292 while (*
p && gql_name_char(*
p,
false))
p++;
304 }
else if (
isdigit((
unsigned char)*
p) || *
p ==
'-') {
309 }
else if (
strncmp(
p,
"true", 4) == 0 && !gql_name_char(
p[4],
false)) {
312 }
else if (
strncmp(
p,
"false", 5) == 0 && !gql_name_char(
p[5],
false)) {
315 }
else if (
strncmp(
p,
"null", 4) == 0 && !gql_name_char(
p[4],
false)) {
319 gql_add_error(errors,
"Unsupported argument value format");
349 unsigned int depth) {
351 gql_add_error(errors,
"Query selection depth exceeds maximum allowed");
355 p = skip_ws_comments(
p);
356 if (*
p !=
'{')
return p;
359 size_t field_count = 0;
361 p = skip_ws_comments(
p);
362 if (*
p ==
'}' || *
p ==
'\0') {
368 gql_add_error(errors,
"Field count limit exceeded");
374 if (!gql_name_char(*
p,
true)) {
375 gql_add_error(errors,
"Invalid field name syntax");
378 while (*
p && gql_name_char(*
p,
false))
p++;
390 p = skip_ws_comments(
p);
393 p = skip_ws_comments(
p);
395 if (!gql_name_char(*
p,
true)) {
396 gql_add_error(errors,
"Invalid field name after alias");
399 while (*
p && gql_name_char(*
p,
false))
p++;
416 gql_add_error(errors,
err_buf);
427 p = skip_ws_comments(
p);
437 else if (*
p ==
'}') {
482 return gql_error(-1);
491 gql_add_error(errors,
"GraphQL request must contain a string 'query'");
493 gql_add_error(errors,
"Query string exceeds maximum allowed size");
495 const char *
p = skip_ws_comments(query->valuestring);
498 if (
strncmp(
p,
"mutation", 8) == 0 && !gql_name_char(
p[8],
false)) {
501 p = skip_ws_comments(
p);
503 while (*
p && *
p !=
'{')
p++;
504 }
else if (
strncmp(
p,
"query", 5) == 0 && !gql_name_char(
p[5],
false)) {
507 p = skip_ws_comments(
p);
508 while (*
p && *
p !=
'{')
p++;
512 gql_add_error(errors,
"Expected '{' to begin selection set");
528 if (!
printed)
return gql_error(-1);
533 return gql_error(-1);
563 const char *
p = skip_ws_comments(query);
564 if (
strncmp(
p,
"subscription", 12) != 0 || gql_name_char(
p[12],
false))
return false;
566 p = skip_ws_comments(
p);
569 while (*
p && *
p !=
'{')
p++;
570 if (*
p !=
'{')
return false;
572 p = skip_ws_comments(
p);
573 if (*
p ==
'}' || !gql_name_char(*
p,
true))
return false;
577 while (*
p && gql_name_char(*
p,
false))
p++;
583 p = skip_ws_comments(
p);
586 p = skip_ws_comments(
p);
588 if (!gql_name_char(*
p,
true))
return false;
589 while (*
p && gql_name_char(*
p,
false))
p++;
601 if (!errors)
return false;
Memory allocation wrappers backed by libttak.
void cwist_free(void *ptr)
Free memory obtained via cwist_alloc/cwist_strdup/etc.
char * cwist_strdup(const char *src)
Duplicate a string using libttak-backed storage.
void * cwist_alloc(size_t size)
Allocate zeroed memory tracked by libttak.
#define CWIST_SCRATCH_DEFER
Attach to a cwist_scratch_t variable's declaration so its allocation (arena- or heap-backed,...
Safe string type with helper methods.
cwist_sstring * cwist_sstring_create(void)
Create a new sstring instance.
cwist_error_t cwist_sstring_assign_len(cwist_sstring *str, const char *data, size_t len)
Assign raw bytes with length.
cwist_error_t cwist_sstring_assign(cwist_sstring *str, const char *data)
Assign a C string.
void cwist_sstring_destroy(cwist_sstring *str)
Destroy an sstring instance.
@ CWIST_ERR_INT16
used when checking common errcodes in Unix/Linux
bool cwist_graphql_add_mutation(cwist_graphql_schema_t *schema, const char *field, cwist_graphql_resolver_fn resolver, void *ctx)
Register a resolver for a GraphQL mutation field.
bool cwist_graphql_parse_subscription(const char *query, const cJSON *variables, char *field_out, size_t field_cap, cJSON **args_out)
Extract the root field name and parsed arguments of a subscription operation.
struct graphql_field graphql_field_t
bool cwist_graphql_add_query(cwist_graphql_schema_t *schema, const char *field, cwist_graphql_resolver_fn resolver, void *ctx)
Register a resolver for a GraphQL query field.
void cwist_graphql_schema_destroy(cwist_graphql_schema_t *schema)
Destroy a schema and all registered query/mutation fields.
cwist_error_t cwist_graphql_execute(cwist_graphql_schema_t *schema, const char *request_json, cwist_sstring **out_json)
Execute a GraphQL query or mutation from a JSON request body.
cwist_graphql_schema_t * cwist_graphql_schema_create(void)
Allocate an empty GraphQL schema with no query or mutation fields.
void cwist_graphql_serve(cwist_graphql_schema_t *schema, cwist_http_request *req, cwist_http_response *res)
HTTP handler serving GraphQL requests over POST.
cwist_error_t cwist_http_header_add(cwist_http_header_node **head, const char *key, const char *value)
Add a header to the list.
Full-featured, production-ready GraphQL query & mutation execution engine.
#define CWIST_GRAPHQL_MAX_FIELDS
struct cwist_graphql_schema cwist_graphql_schema_t
cJSON *(* cwist_graphql_resolver_fn)(const cJSON *args, const cJSON *variables, void *ctx)
#define CWIST_GRAPHQL_MAX_QUERY_SIZE
#define CWIST_GRAPHQL_MAX_DEPTH
Integrated error structure used throughout the system.
cwist_http_method_t method
HTTP Response Object. Supports standard string body or Zero-Copy pointer body.
cwist_http_status_t status_code
cwist_http_header_node * headers
Backing state for cwist_scratch_alloc()/CWIST_SCRATCH_DEFER: a single block-scoped allocation that pr...
char * data
Access directly only when raw handling is necessary.