Skip to content

Commit 27d08f6

Browse files
Merge pull request #22 from rockneurotiko/support_apollo_federation
Support Apollo Federation
2 parents 3e5f45b + 5ee8712 commit 27d08f6

File tree

14 files changed

+2068
-23
lines changed

14 files changed

+2068
-23
lines changed

.tool-versions

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
erlang 28.1.1
2-
elixir 1.19.2-otp-28
1+
erlang 28.3
2+
elixir 1.19.4-otp-28

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
All notable changes to this project will be documented in this file.
44

55
## [Unreleased]
6+
### Added
7+
- Add `federation` and `F` options, this will read `@link` directive and add Apollo Federation directives based to parse and validate the schema
8+
69
### Fixed
710
- When reading an absinthe schema, print the errors if there were some compilation error
811

README.md

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ GraphQL Query provides a library for **validating, parsing, and formatting Graph
3232
- [Parsing and Validating Schemas](#parsing-and-validating-schemas)
3333
- [Schema Modules](#schema-modules)
3434
- [Document Validation Against Schema](#document-validation-against-schema)
35+
- [Apollo Federation Support](#apollo-federation-support)
3536
- [Formatter Integration](#formatter-integration)
3637
- [Manual API](#manual-api)
3738
- [Roadmap](#roadmap)
@@ -64,6 +65,7 @@ GraphQL Query provides a library for **validating, parsing, and formatting Graph
6465
-**Mix format integration** for `~GQL` sigil, `.graphql` and `.gql` files
6566
-**Schema modules** with automatic recompilation on schema changes
6667
-**Absinthe schema integration** for validating queries against existing Absinthe schemas
68+
-**Apollo Federation v2 support** for validating federated schemas with `@key`, `@shareable`, etc.
6769
-**Flexible validation modes**: compile-time, runtime, or ignore
6870
-**JSON encoding support** for Document structs (JSON/Jason protocols)
6971
- ⚡ Backed by Rust for fast parsing and validation
@@ -172,6 +174,7 @@ Req.post!("/api", json: user_query)
172174
- `s` → Parse as schema
173175
- `q` → Parse as query (this is the default behaviour)
174176
- `f` → Parse as fragment
177+
- `F` → Enable Apollo Federation v2 directives (for schemas)
175178

176179
```elixir
177180
import GraphqlQuery
@@ -232,6 +235,7 @@ query GetUser($id: ID!) {
232235
- `schema: SchemaModule` → Specify the schema module to validate the query or fragment with
233236
- `fragments: [GraphqlQuery.Fragment.t()]` → Add reusable fragments to queries
234237
- `format: true` → Apply automatic formatting when converting to string
238+
- `federation: true` → Enable Apollo Federation v2 directive support (for schemas)
235239

236240
Example project structure:
237241

@@ -276,6 +280,7 @@ end
276280
- `schema: SchemaModule` → Specify the schema module to validate the query or fragment with
277281
- `fragments: [GraphqlQuery.Fragment.t()]` → Add reusable fragments to queries
278282
- `format: true` → Apply automatic formatting when converting to string
283+
- `federation: true` → Enable Apollo Federation v2 directive support (for schemas)
279284

280285
```elixir
281286
defmodule Example do
@@ -629,6 +634,103 @@ end
629634

630635
---
631636

637+
## Apollo Federation Support
638+
639+
GraphQL Query supports **Apollo Federation v2** directives, allowing you to validate federated schemas that use directives like `@key`, `@shareable`, `@external`, and others.
640+
641+
### Enabling Federation Support
642+
643+
Enable federation validation by setting the `federation: true` option. This makes all Apollo Federation v2 directives available in your schema.
644+
645+
**With sigil (using `F` modifier):**
646+
647+
```elixir
648+
~GQL"""
649+
extend schema
650+
@link(url: "https://specs.apollo.dev/federation/v2.5", import: ["@key", "@shareable"])
651+
652+
type User @key(fields: "id") {
653+
id: ID!
654+
name: String! @shareable
655+
}
656+
"""sF # s = schema, F = federation
657+
```
658+
659+
**With `gql` macro:**
660+
661+
```elixir
662+
gql [type: :schema, federation: true], """
663+
extend schema
664+
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])
665+
666+
type Product @key(fields: "id") {
667+
id: ID!
668+
price: Float
669+
}
670+
"""
671+
```
672+
673+
**With `gql_from_file` macro:**
674+
675+
```elixir
676+
gql_from_file "priv/graphql/federated_schema.graphql", type: :schema, federation: true
677+
```
678+
679+
**With `document_with_options`:**
680+
681+
```elixir
682+
document_with_options type: :schema, federation: true do
683+
~GQL"""
684+
extend schema @link(url: "https://specs.apollo.dev/federation/v2.9", import: ["@key"])
685+
686+
type Order @key(fields: "id") {
687+
id: ID!
688+
total: Float
689+
}
690+
"""s
691+
end
692+
```
693+
694+
**At module level:**
695+
696+
```elixir
697+
defmodule MyApp.FederatedSchema do
698+
use GraphqlQuery, federation: true
699+
700+
def schema do
701+
~GQL"""
702+
extend schema @link(url: "https://specs.apollo.dev/federation/v2.5", import: ["@key"])
703+
704+
type User @key(fields: "id") {
705+
id: ID!
706+
name: String!
707+
}
708+
"""s
709+
end
710+
end
711+
```
712+
713+
### Supported Federation Versions
714+
715+
The library supports multiple Apollo Federation v2 versions. Specify the version in your `@link` directive:
716+
717+
- `v2.0` - Base Apollo Federation v2 directives
718+
- `v2.5` - Adds `@authenticated` and `@requiresScopes`
719+
- `v2.9` - Adds `@cost` and `@listSize`
720+
721+
Unknown versions automatically fall back to standard validation without federation directives.
722+
723+
### Implementation Note
724+
725+
When `federation: true` is enabled, **all Apollo Federation directives are available** for use in your schema, regardless of what you specify in the `@link` import list. This simplified approach covers the vast majority of use cases and makes it easier to work with federated schemas.
726+
727+
The following directives are available:
728+
- Core directives: `@key`, `@requires`, `@provides`, `@external`, `@tag`, `@extends`, `@shareable`, `@inaccessible`, `@override`, `@composeDirective`, `@interfaceObject`
729+
- Authentication (v2.5+): `@authenticated`, `@requiresScopes`, `@policy`
730+
- Cost control (v2.9+): `@cost`, `@listSize`
731+
732+
---
733+
632734
## Formatter Integration
633735

634736
Add to `.formatter.exs`:

docs/cheatsheet.cheatmd

Lines changed: 177 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ defmodule MyApp.Queries do
2525
ignore: false,
2626
evaluate: false,
2727
fragments: [],
28-
format: false
28+
format: false,
29+
federation: false
2930
end
3031
```
3132
{: .wrap}
@@ -193,6 +194,9 @@ type Query {
193194

194195
# Schema document
195196
~GQL""s
197+
198+
# Federation support (for schemas)
199+
~GQL""sF
196200
```
197201
{: .wrap}
198202

@@ -250,6 +254,9 @@ query = gql_from_file("priv/queries/get_user.graphql", runtime: true)
250254

251255
# Apply formatting when transformed to string
252256
query = gql_from_file("priv/queries/get_user.graphql", format: true)
257+
258+
# Enable Apollo Federation directives (for schemas)
259+
schema = gql_from_file("priv/federated_schema.graphql", type: :schema, federation: true)
253260
```
254261
{: .wrap}
255262

@@ -320,6 +327,9 @@ query = gql [runtime: true], ""
320327

321328
# Apply automatic formatting when transforming to string
322329
query = gql [format: true], "query{user{id name}}"
330+
331+
# Enable Apollo Federation directives (for schemas)
332+
schema = gql [type: :schema, federation: true], ""
323333
```
324334
{: .wrap}
325335

@@ -419,6 +429,172 @@ Jason.encode!(query)
419429
```
420430
{: .wrap}
421431

432+
## Apollo Federation
433+
{: .col-2}
434+
435+
### Federation Schema with Sigil
436+
437+
```elixir
438+
# Use the "F" modifier to enable federation directives
439+
~GQL"""
440+
extend schema
441+
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@shareable"])
442+
443+
type User @key(fields: "id") {
444+
id: ID!
445+
name: String! @shareable
446+
email: String!
447+
}
448+
"""sF # <- "s" for schema, "F" for federation
449+
```
450+
{: .wrap}
451+
452+
### Federation with gql Macro
453+
454+
```elixir
455+
# Use federation: true option
456+
schema = gql [type: :schema, federation: true], """
457+
extend schema
458+
@link(url: "https://specs.apollo.dev/federation/v2.5",
459+
import: ["@key", "@authenticated"])
460+
461+
type Product @key(fields: "id") @authenticated {
462+
id: ID!
463+
name: String!
464+
price: Float
465+
}
466+
"""
467+
```
468+
{: .wrap}
469+
470+
### Federation with gql_from_file
471+
472+
```elixir
473+
# Load federated schema from file
474+
schema = gql_from_file(
475+
"priv/federated_schema.graphql",
476+
type: :schema,
477+
federation: true
478+
)
479+
```
480+
{: .wrap}
481+
482+
### Federation with document_with_options
483+
484+
```elixir
485+
document_with_options type: :schema, federation: true do
486+
~GQL"""
487+
extend schema
488+
@link(url: "https://specs.apollo.dev/federation/v2.0",
489+
import: ["@key"])
490+
491+
type Order @key(fields: "id") {
492+
id: ID!
493+
total: Float
494+
}
495+
"""s
496+
end
497+
```
498+
{: .wrap}
499+
500+
### Module-level Federation
501+
502+
```elixir
503+
defmodule MyApp.FederatedSchema do
504+
use GraphqlQuery, federation: true
505+
506+
def schema do
507+
~GQL"""
508+
extend schema
509+
@link(url: "https://specs.apollo.dev/federation/v2.0",
510+
import: ["@key", "@external"])
511+
512+
type User @key(fields: "id") {
513+
id: ID!
514+
reviews: [Review!]! @external
515+
}
516+
"""s
517+
end
518+
end
519+
```
520+
{: .wrap}
521+
522+
### Supported Federation Versions
523+
524+
```elixir
525+
# Federation v2.0 - Base directives
526+
~GQL"""
527+
extend schema
528+
@link(url: "https://specs.apollo.dev/federation/v2.0",
529+
import: ["@key", "@requires", "@provides", "@external",
530+
"@tag", "@extends", "@shareable", "@inaccessible",
531+
"@override"])
532+
"""sF
533+
534+
# Federation v2.5 - Adds authentication
535+
~GQL"""
536+
extend schema
537+
@link(url: "https://specs.apollo.dev/federation/v2.5",
538+
import: ["@authenticated", "@requiresScopes"])
539+
"""sF
540+
541+
# Federation v2.9 - Adds cost control
542+
~GQL"""
543+
extend schema
544+
@link(url: "https://specs.apollo.dev/federation/v2.9",
545+
import: ["@cost", "@listSize"])
546+
"""sF
547+
```
548+
{: .wrap}
549+
550+
### Federation with Renamed Directives
551+
552+
```elixir
553+
# Rename directives with "as:"
554+
~GQL"""
555+
extend schema
556+
@link(url: "https://specs.apollo.dev/federation/v2.0",
557+
import: [{name: "@key", as: "@primaryKey"}])
558+
559+
type Product @primaryKey(fields: "id") {
560+
id: ID!
561+
name: String!
562+
}
563+
"""sF
564+
```
565+
{: .wrap}
566+
567+
### Federation with Custom Namespace
568+
569+
```elixir
570+
# Use custom namespace prefix
571+
~GQL"""
572+
extend schema
573+
@link(url: "https://specs.apollo.dev/federation/v2.0",
574+
import: ["@key"], as: "fed")
575+
576+
type User @key(fields: "id") {
577+
id: ID!
578+
# Non-imported directives use the prefix
579+
name: String! @fed__shareable
580+
}
581+
"""sF
582+
```
583+
{: .wrap}
584+
585+
### Federation Options in use GraphqlQuery
586+
587+
```elixir
588+
defmodule MyApp.Schemas do
589+
use GraphqlQuery,
590+
federation: true,
591+
# ... other options
592+
schema: nil,
593+
runtime: false
594+
end
595+
```
596+
{: .wrap}
597+
422598
## Full example!
423599

424600
#### schema.ex

0 commit comments

Comments
 (0)