Repository navigation
GraphQL
Headers: <cwist/net/graphql/graphql.h> (queries and mutations),
<cwist/net/graphql/graphql_ws.h> (subscriptions). <cwist/graphql.h> and
<cwist/graphql_ws.h> include them.
CWIST's GraphQL layer maps top-level Query, Mutation and Subscription
fields to C resolvers that return cJSON values. It is bounded by design: no
schema language, no introspection and no type system beyond what the resolvers
return.
What the executor handles: root fields with arguments (literals or
$variables), aliases, several root fields in one operation, named operations
and mutation operations. A resolver's return value is placed in data as
is.
What it does not handle: selection sets on a field's result. A query such as
{ user(id: "7") { id name } } currently returns the resolver's object
together with "Cannot query field" errors for the sub-fields (and the
sub-field names repeated with null values). Query the root field without a
selection set ({ user(id: "7") }) and have the resolver return the shape
you want.
#include <cwist/app.h>
#include <cwist/net/graphql/graphql.h>
static cJSON *user(const cJSON *args, const cJSON *variables, void *ctx) {
(void)variables; (void)ctx;
const cJSON *id = cJSON_GetObjectItemCaseSensitive(args, "id");
cJSON *o = cJSON_CreateObject();
cJSON_AddStringToObject(o, "id", cJSON_IsString(id) ? id->valuestring : "?");
cJSON_AddStringToObject(o, "name", "Ada");
return o; /* CWIST takes ownership */
}
static cwist_graphql_schema_t *schema;
static void graphql(cwist_http_request *req, cwist_http_response *res) {
cwist_graphql_serve(schema, req, res);
}
int main(void) {
schema = cwist_graphql_schema_create();
cwist_graphql_add_query(schema, "user", user, NULL);
cwist_app *app = cwist_app_create();
cwist_app_post(app, "/graphql", graphql);
cwist_app_listen(app, 8080);
cwist_app_destroy(app);
cwist_graphql_schema_destroy(schema);
}curl -s localhost:8080/graphql -H 'Content-Type: application/json' \
-d '{"query":"{ user(id: \"7\") }"}'
# {"data":{"user":{"id":"7","name":"Ada"}}}| Function | Description |
|---|---|
cwist_graphql_schema_t *cwist_graphql_schema_create(void) / void cwist_graphql_schema_destroy(cwist_graphql_schema_t *s)
|
Schema lifecycle. |
bool cwist_graphql_add_query(cwist_graphql_schema_t *s, const char *field, cwist_graphql_resolver_fn fn, void *ctx) |
Register or replace a Query field. |
bool cwist_graphql_add_mutation(cwist_graphql_schema_t *s, const char *field, cwist_graphql_resolver_fn fn, void *ctx) |
Register or replace a Mutation field. |
cwist_error_t cwist_graphql_execute(cwist_graphql_schema_t *s, const char *request_json, cwist_sstring **out_json) |
Execute a JSON request (query, optional operationName and variables) and produce the JSON response. |
void cwist_graphql_serve(cwist_graphql_schema_t *s, cwist_http_request *req, cwist_http_response *res) |
Handler adapter for POST routes. The body must be the JSON request object ({"query": ...}); other methods and bodies get 400. |
typedef cJSON *(*cwist_graphql_resolver_fn)(const cJSON *args, const cJSON *variables, void *ctx);args is the field's parsed arguments (or NULL), variables the request
variables. Return a new cJSON value; CWIST frees it. Resolvers run on the
request thread and can run concurrently.
Limits: CWIST_GRAPHQL_MAX_QUERY_SIZE (64 KiB), CWIST_GRAPHQL_MAX_FIELDS
(128) and CWIST_GRAPHQL_MAX_DEPTH (16).
Supported since v3.8 and opt-in. Subscriptions use the
graphql-transport-ws protocol over the reactor WebSocket transport, so they
need the reactor server (C1M mode).
Protocol behavior:
-
connection_initis answered withconnection_ack; asubscribebefore init closes the socket with code4429. -
subscribe {id, payload:{query, variables, operationName}}produces onenext {id, payload:{data:{...}}}per published event,error {id, payload:[...]}when the operation cannot start, andcomplete {id}when it ends. -
ping/pongkeepalive after init. Malformed messages close with4400/4401; a duplicate operation id closes with4409. - A client
completeor a socket close tears the subscription down.
#include <cwist/net/graphql/graphql_ws.h>
static cwist_graphql_event_source_t *counter_subscribe(const cJSON *args, const cJSON *vars, void *ctx) {
(void)args; (void)vars; (void)ctx;
cwist_graphql_event_source_t *src = cwist_graphql_event_source_create();
cwist_graphql_event_source_add_topic(src, "counter");
return src; /* NULL rejects the operation */
}
cwist_graphql_ws_t *gws = cwist_graphql_ws_create(schema);
cwist_graphql_ws_add_subscription(gws, "counter", counter_subscribe, NULL);
/* hand an upgraded socket to the endpoint: */
cwist_graphql_ws_attach(gws, fd, reactor, initial_bytes, initial_len);
/* anywhere, any thread: */
cJSON *payload = cJSON_CreateNumber(42);
cwist_graphql_publish("counter", payload); /* -> next {data:{counter: 42}} */
cJSON_Delete(payload);| Function | Description |
|---|---|
cwist_graphql_ws_t *cwist_graphql_ws_create(cwist_graphql_schema_t *schema) |
Endpoint bound to a schema (not owned; must outlive the endpoint). |
void cwist_graphql_ws_destroy(cwist_graphql_ws_t *gws) |
Destroy after every attached connection is gone (after the reactors stopped). |
bool cwist_graphql_ws_add_subscription(gws, field, subscribe_fn, ctx) |
Register or replace a subscription field. The resolver runs on the reactor thread for each subscribe. |
bool cwist_graphql_ws_attach(gws, fd, reactor, initial, initial_len) |
Hand an upgraded socket to the protocol handler; the HTTP layer must not touch the fd afterwards. false closes the fd. |
cwist_graphql_event_source_create, _add_topic, _destroy
|
Topic set a subscription listens to; destroyed automatically when the operation ends. |
size_t cwist_graphql_publish(const char *topic, const cJSON *payload) |
Publish to the in-process broker (payload borrowed; NULL sends null). Thread-safe; delivery happens on each connection's reactor thread. Returns the number of next messages queued. |
bool cwist_graphql_parse_subscription(query, variables, field_out, field_cap, &args_out) |
Extract the root field and arguments of a subscription operation (used by the protocol layer). |
The broker is in-process: events published in one worker process reach only
subscribers connected to that process. Tests:
tests/test_graphql.c, tests/test_graphql_subscriptions.c.
CWIST wiki, written against the dev branch of c4punks/CWIST. Pages marked "Source:" are generated from files under docs/; fix those in the repository. Questions: Discord.
Getting started
- Installation
- Quick Start
- Linking
- Server Modes
- Configuration and Environment
- Project CLI
- Tutorial / Korean
- Examples and Tutorials
Guides
Core
- API Reference
- Application
- Routing
- Middleware
- Requests and Responses
- Async Handlers
- Streaming Responses
- Error Handling
- Graceful Shutdown
- Multiport
Protocols
- HTTPS and TLS
- HTTP/2
- HTTP/3 and QUIC
- WebTransport
- WebSocket
- Server-Sent Events
- gRPC Server
- gRPC Client
- Protobuf and Codegen
- GraphQL
- WebRTC DataChannels
- HTTP Clients
Web features
- Static Files and Assets
- Big Dumb Reply Cache
- Compression
- Cookies
- Sessions and Flash
- Query Maps
- Multipart Uploads
- HTML Components
- Templates
- JSON
- Validation
- OpenAPI
Security
Data
Runtime
- Memory Management
- Full GC
- Async GC Ownership
- SString
- Reactor and I/O
- Metrics and Health
- Logging
- Testing
Platforms
Performance notes
- Benchmark Methodology
- C1M File Limits
- Reactor Fairness
- Cooperative Queuing
- Classic Pool Starvation
- Reactor Wakeup
- wrk Dual Histogram
- FIXED Endpoint Cache
- ADR-0001
- Durable Queue Gate
- Mux References
Project