A graph served for something else (MountGraphFor) now gets what a
second engine had been written for:
- The request is gathered before anything resolves: fragments spread
once per selection set, cycles and unknown fragments refused at
parse, @skip and @include decided (on inline fragments too), fields
under one key merged, variables put in place. A Resolver receives
the field as a Selection with its whole subtree, so it can fetch
only what is selected or refuse a request too deep to answer.
- An Object answer is asked for each selected field with that field's
own arguments, so nesting can be traversal. Selection.Bind decodes
arguments into a struct, refusing unknown ones.
- The schema is rendered per request with the caller's context, and a
schema that fails is a 500 that says so.
- Bounds on the gathered tree refuse whole: 64 root fields, 10000
fields in all, depth 64.
- A query's root fields resolve at once; a mutation's in order. A
panic fails its field.
- Keys go out in the order selected. A given variable the operation
does not declare is ignored, and a declared one given nothing leaves
its argument out, both as the specification says.
- MountGraphFor's routes are declared, so a host routes to them and a
document describes them; Headers(ctx) relays a request whole.
GraphQLSDL published what the ops describe; nothing ran it. This adds the
executor and mounts both halves on one path.
The resolver is op.direct, so a field runs the contract the REST route runs:
validate, authorize, handler. Two properties are enforced rather than merely
published, and each has a test that fails when the enforcement is removed:
- A query operation resolves names against GET ops only. A mutating op is
absent from the index a query searches, so it cannot be invoked by one.
- A `header:` field is ambient. The schema does not publish it as an
argument, an argument naming it is refused, and its value is bound from
the request after the arguments — what the gateway sets, a client cannot
supply.
The projection carries the Go type alongside the decoded value, which is what
separates a field absent because it was empty (null) from a field absent from
the type (an error). Without it every misspelling silently answers null.
The reader is hand-written: a dependency here is a dependency in every service
that links zip, to read a grammar whose executable half is six token kinds and
five productions. It takes operations, variables, aliases, fragments, inline
fragments and @skip/@include. Subscriptions and introspection are refused by
name — no op is a stream, and the schema is published whole at GET.
Also corrects the schema walk to use wireFields, so an embedded struct's
promoted fields appear, and renders time.Time as String rather than by fields
that never cross the wire.
Co-authored-by: Hanzo Dev <[email protected]>