Skip to content

Add the GraphQL SDL and Apollo Federation compiler#20

Open
OmarAlJarrah wants to merge 5 commits into
mainfrom
feat/graphql-compiler
Open

Add the GraphQL SDL and Apollo Federation compiler#20
OmarAlJarrah wants to merge 5 commits into
mainfrom
feat/graphql-compiler

Conversation

@OmarAlJarrah

Copy link
Copy Markdown
Member

Summary

Adds compilers/graphql, a compiler that lowers GraphQL SDL schemas — plain
schemas and Apollo Federation v1/v2 subgraphs — into the spec-agnostic IR. It is
the GraphQL counterpart to compilers/openapi and follows the same shape: a
Compiler implementing compilers.Compiler, a pointer-derived identity scheme
(ids.go), typed diagnostics with stable slash-namespaced codes (diag.go),
per-compile options (options.go), and no file I/O — bytes arrive in
compilers.Source.

Parsing uses github.com/vektah/gqlparser/v2 at the SDL level
(parser.ParseSchemas), so the compiler sees exactly what is written: no
introspection built-ins are injected, undefined federation directives never
abort the parse, and extend occurrences arrive separately and are assembled
with per-member provenance.

GraphQL coverage

  • Objects & interfaces → Model; interfaces are Abstract, and
    implements A & B (including interfaces implementing interfaces) populates
    Model.Implements.
  • Input objects → InputOnly models; @oneOf inputs → Union{Exclusive, WireTagged} with a variant per field (key-tagged, no internal discriminator),
    never a model of optional fields.
  • Union types → __typename-tagged Union with an internal discriminator
    mapping each member to its type.
  • Enums → closed string Enum; custom scalars → opaque nil-base
    Scalar
    (with @specifiedBy preserved); built-in scalars → primitives,
    with ID kept as a distinct named string scalar so it is not conflated with
    String.
  • Fields → Property, arguments → Property.Args at any depth; non-null
    ! maps to Required + a non-nullable ref and its absence to a nullable ref,
    applied per layer so every [T!]! combination is captured. List wrappers hoist
    real List nodes. Defaults land on the Values channel as exact BigVals (no
    float64); @deprecatedDeprecation; descriptions → Docs.
  • Root types → one service with query/mutation/subscription groups; each
    field becomes an Operation with a GraphQLBinding. Query fields are marked
    side-effect-free; subscriptions carry StreamingServer + ResponseStream.
  • Directive applications are preserved as ordered argument arrays under the
    graphql: namespace, with the directive-definition inventory at document
    level.

Federation coverage (v1 and v2)

  • Version is detected from the @link to the federation spec URL (v2); a v1
    subgraph using federation directives without @link is detected as v1.
  • @key, @shareable, @external, @requires, @provides, @override,
    @interfaceObject, @composeDirective, @extends, and @tag are preserved
    losslessly under the federation: extension namespace; @key marks entities.
  • @inaccessible additionally lowers to Access = "internal" on types and
    Visibility.None on fields, alongside verbatim preservation.
  • extend type/extend schema are assembled into their base (a base owned by
    another subgraph is synthesized), with the extend occurrences recorded under
    graphql:extends.

Where GraphQL has no structural home in the IR, information is preserved in
namespaced Extensions rather than dropped: the built-in ID scalar carries a
graphql:builtin-scalar marker, and a @oneOf-derived union carries a
graphql:oneOfInput marker so an emitter can recover its input-only nature
(Union has no InputOnly field). No IR structs were changed.

Test plan

  • Golden IR snapshot of a full social-network schema exercising the whole
    surface (testdata/golden/graphql/social.graphql).
  • Conformance corpus: one minimal schema per capability row of the spec
    matrix that GraphQL/Federation can express — objects, interfaces, unions,
    @oneOf inputs, enums, custom scalars, input objects, field arguments, the
    four nullability states, recursive types, the operation surface, deprecation,
    directives, docs, and federation v1 and v2 — each with a focused
    capability-specific assertion plus a byte-exact golden IR snapshot.
  • Round-trip property: every corpus document plus the golden schema
    round-trips compile → MarshalJSON → UnmarshalJSON → MarshalJSON byte-stably.
  • Architecture test: internal/archtest gains a compilers/graphql rule
    restricting its imports to ir, the compiler contract, and gqlparser.

go build ./..., go vet ./..., gofmt -l ., golangci-lint run, and
go test ./... (including -race on the new package) are all clean.

OmarAlJarrah and others added 5 commits July 21, 2026 03:59
… the IR

Add a GraphQL compiler that maps SDL schemas onto the spec-agnostic IR, the
GraphQL counterpart to the OpenAPI compiler. It parses at the SDL level so it
sees exactly what is written: no introspection built-ins are injected, undefined
federation directives never abort the parse, and `extend` occurrences are
assembled with per-member provenance.

Type mapping:
- object and interface types become models (interfaces are Abstract;
  `implements A & B` populates Implements);
- input objects become InputOnly models, and `@oneOf` inputs become key-tagged
  exclusive unions rather than models of optional fields;
- union types become `__typename`-tagged unions;
- enums become closed string enums; custom scalars become opaque nil-base
  scalars; built-in scalars map to primitives, with ID kept as a distinct named
  string scalar;
- non-null `!` maps to Required plus a non-nullable ref and its absence to a
  nullable ref, per layer, so every `[T!]!` combination is captured;
- field arguments become Property.Args at any depth; defaults, `@deprecated`,
  and descriptions map to the Values channel, Deprecation, and Docs.

The three root types become one service with query, mutation, and subscription
groups; subscriptions carry server-streaming semantics and a GraphQLBinding.

Federation v1 and v2 are both supported, with v2 detected from the `@link` to
the federation spec URL. Entities (`@key`), `@shareable`, `@external`,
`@requires`, `@provides`, `@override`, `@interfaceObject`, `@composeDirective`,
and `@tag` are preserved losslessly under the `federation:` extension namespace;
`@inaccessible` additionally lowers to Access/Visibility. All other directive
applications are preserved under `graphql:` as ordered argument arrays, and
directive definitions under a document-level inventory.

Tests: a golden IR snapshot of a full schema; a conformance corpus of one
minimal schema per capability row (GraphQL and Federation) with focused
assertions and byte-exact goldens; and a round-trip property over every corpus
document.
Make the SDL parser an injectable per-instance seam so the parser-panic recovery
path is exercisable without any mutable global state, enrich the input-object and
directive fixtures to cover every literal kind (list, object, enum symbol, and
boolean defaults, plus directive arguments of each kind), and add white-box tests
for the pure helpers and defensive branches (naming, identity, provenance, value
lowering and JSON rendering, extension assembly, and the load error paths).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant