Architecture
Gurafi is split into layers with one rule: lower layers never know about higher layers.
This is what makes it possible to build another high-level API or another
transport without forking the engine. Nothing in gql names gurafi, suku,
axum, a database or an authentication scheme.
gql: the specification
gql is a reference implementation of the GraphQL October 2021
specification. Its public surface is
explicit and stable:
Design decisions
- Names, not pointers. A field's type references another type by name. Recursive schemas therefore need no interior mutability. The builder resolves and validates every name once.
- Immutable schemas.
Schemais cheap to clone (Arcinside) and safe to share across threads and requests. - Values are explicit. Execution produces
Value, not JSON. Transports decide how to serialize; tests can assert structure without a JSON round trip. - No hidden panics. Malformed input, invalid schemas and failed coercion
become
GraphQLErrors; the engine itself does not panic on them. A panic in user resolver code (a field resolver, a subscription setup resolver,resolve_typeoris_type_of) is caught at that boundary and reported as a field error withextensions.codeINTERNAL_SERVER_ERROR, so it cannot unwind a request or end a subscription stream. Public APIs do not useunsafe. - Errors are data.
GraphQLErrorcarries source locations, response paths and extensions, and implementsserde::Serializewhen theserdefeature is enabled. - Opaque model values.
Value::Externallets a type-safe layer pass its own Rust models (User,Post, …) between resolvers. The engine never tries to serialize them.
Execution pipeline
Every stage can short-circuit with specification-shaped errors. Resolvers are only reached by a valid operation with valid variables.
Performance
- The parser borrows names and string literals from the source.
- Names are interned once per schema (
Arc<str>). - Object values use an inline
SmallVec, avoiding a heap allocation for the common small-object case. - Field collection groups by response key once per selection set and deduplicates fragments.
- Sibling fields on queries resolve concurrently with a bounded limit; mutations stay serial.
- Introspection results are descriptors, not copies of the whole schema.
- Complexity analysis runs before execution, so expensive operations are rejected before any resolver or database call.
- Fields declared with
gurafi'sexpose!use a synchronous property resolver. The executor calls it inline on the leaf fast path and the general completion path, so a plain model read allocates no future (and no path clone or batch slot). A field whose options need a future — auth, async auth, validation or awrap_resolveplugin — is converted to the boxed path once, at build time.
gurafi: developer experience
gurafi exists to make the common case feel like writing Rust, not like
describing a schema:
- Rust types infer GraphQL types (
String→String!,Option<T>→ nullable,Vec<T>→ list,Result<T, E>in fallible resolvers). #[derive(Object)],#[derive(Input)]/#[derive(Args)],#[derive(Enum)],#[derive(Scalar)]and#[derive(Union)]connect Rust models to the engine.expose!(Model::field)creates a field from a Rust field with its name and type inferred.- Field args are derived from a typed input struct; argument declarations are
not duplicated. An input is args-only unless it is explicitly named, so no
FriendsArgstype leaks into the schema. - Types and roots can be reopened across modules (
object_fields,interface_fields,query_fields, ...), so declarations merge naturally. .auth(...),.complexity(...),.description(...),.deprecated(...),.nullable(),.non_null()and.as_type::<T>()attach the same metadata with build-time checks.- Object- and interface-level authorization, validation hooks, one-of inputs and resolver info are first-class.
- Interfaces, unions, enums, custom scalars, custom directives and subscriptions are first-class.
- Plugins provide build-time field hooks for tracing, mocks, scopes and response extensions.
- Runtime-named references support Relay connections, payloads and generated result unions.
LoaderandRequestCacheprovide request-scoped batching and caching.#[derive(ErrorUnion)]andfield_result_unionexpose typed failures as GraphQL unions.
Everything gurafi produces is a normal gql::Schema; suku (or any other
transport) cannot tell the difference.
gurafi-relay: Relay conventions
gurafi-relay is a thin layer over the gurafi builder:
- typed global IDs and the shared
Nodeinterface node/nodesdispatch with request-scoped caching- connections, cursor pagination and
PageInfo - mutation payloads with
clientMutationId
It uses runtime-named object references, so connection and payload type names do not need code generation.
suku: transport
suku turns a gql::Schema into an HTTP service:
- POST/GET GraphQL over HTTP with correct content negotiation and status codes
- JSON batching
- Server-sent-event subscriptions
- GraphiQL
- Request limits (body, depth, complexity, errors, batch size)
- A context extractor for authentication (Bearer tokens, cookies, headers)
- Field authorization through
gql::Authorizer
suku has no schema-building logic and no knowledge of gurafi types.
Testing philosophy
- Parser tests cover the full grammar and lexical edge cases.
- Validation tests cover every specification rule.
- Execution tests cover aliases, fragments, directives, variables, coercion, lists, null propagation, abstract types, mutations and subscriptions.
- Introspection tests compare against the standard introspection query.
- Schema diff tests pin backwards-compatibility behaviour.
gurafitests exercise the public API end to end, including derive macros.sukutests exercise HTTP behaviour without opening sockets where possible.
