diff --git a/.cargo/config.toml b/.cargo/config.toml
index c00438ac33..d9af243cb8 100644
--- a/.cargo/config.toml
+++ b/.cargo/config.toml
@@ -1,2 +1,3 @@
[alias]
xtask = "run --locked --package xtask --manifest-path xtask/Cargo.toml --"
+fed = "run -p apollo-federation-cli --"
diff --git a/.circleci/config.yml b/.circleci/config.yml
index cb5214a584..0a5db986b6 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -28,21 +28,18 @@ executors:
resource_class: xlarge
environment:
CARGO_BUILD_JOBS: 4
- RUST_TEST_THREADS: 6
arm_linux_build: &arm_linux_build_executor
machine:
image: ubuntu-2004:2024.01.1
resource_class: arm.large
environment:
CARGO_BUILD_JOBS: 8
- RUST_TEST_THREADS: 8
arm_linux_test: &arm_linux_test_executor
machine:
image: ubuntu-2004:2024.01.1
resource_class: arm.xlarge
environment:
CARGO_BUILD_JOBS: 8
- RUST_TEST_THREADS: 8
macos_build: &macos_build_executor
macos:
# See https://circleci.com/docs/xcode-policy along with the support matrix
@@ -495,7 +492,7 @@ commands:
environment:
# Use the settings from the "ci" profile in nextest configuration.
NEXTEST_PROFILE: ci
- command: xtask test --workspace --locked
+ command: xtask test --workspace --locked --features ci
- run:
name: Delete large files from cache
command: |
@@ -505,10 +502,10 @@ commands:
condition:
equal: [ "dev", "<< pipeline.git.branch >>" ]
steps:
- - save_cache:
- key: "<< pipeline.parameters.merge_version >>-test-<< parameters.variant >>"
- paths:
- - target
+ - save_cache:
+ key: "<< pipeline.parameters.merge_version >>-test-<< parameters.variant >>"
+ paths:
+ - target
- store_test_results:
# The results from nextest that power the CircleCI Insights.
path: ./target/nextest/ci/junit.xml
@@ -540,7 +537,7 @@ jobs:
steps:
- when:
condition:
- equal: [*amd_linux_helm_executor, << parameters.platform >>]
+ equal: [ *amd_linux_helm_executor, << parameters.platform >> ]
steps:
- checkout
- xtask_check_helm
@@ -838,7 +835,7 @@ jobs:
not:
equal: [ "https://github.com/apollographql/router", << pipeline.project.git_url >> ]
steps:
- - run:
+ - run:
command: >
echo "Not publishing any github release."
- when:
diff --git a/.config/nextest.toml b/.config/nextest.toml
index 8ea7636d0c..828949aecf 100644
--- a/.config/nextest.toml
+++ b/.config/nextest.toml
@@ -3,9 +3,28 @@
# of the run (for easy scrollability).
failure-output = "immediate-final"
+# Repeat non-pass status at the end so they’re easier to find.
+final-status-level = "skip"
+
# Do not cancel the test run on the first failure.
fail-fast = false
+# Most tests should take much less than 2 minute (see override below)
+slow-timeout = { period = "30s", terminate-after = 4 }
+
# Write to output for persistence to CircleCI
[profile.ci.junit]
path = "junit.xml"
+
+# Integration tests require more than one thread. The default setting of 1 will cause too many integration tests to run
+# at the same time and causes tests to fail where timing is involved.
+# This filter applies only to to the integration tests in the apollo-router package.
+[[profile.default.overrides]]
+filter = 'package(apollo-router) & kind(test)'
+threads-required = 2
+
+# Scaffold test takes all the test threads as it runs rustc, and takes more time.
+[[profile.default.overrides]]
+filter = 'package(apollo-router-scaffold)'
+threads-required = 'num-test-threads'
+slow-timeout = { period = "60s", terminate-after = 10 }
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index fd44e4c9fc..041ef1f2e8 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -1 +1,2 @@
/docs/ @apollographql/docs
+/apollo-federation/ @dariuszkuc @sachindshinde @goto-bus-stop @SimonSapin
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c26398ac2e..b8b6339055 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,200 @@ All notable changes to Router will be documented in this file.
This project adheres to [Semantic Versioning v2.0.0](https://semver.org/spec/v2.0.0.html).
+# [1.47.0] - 2024-05-21
+
+## 🚀 Features
+
+### Support telemetry selectors with errors ([Issue #5027](https://github.com/apollographql/router/issues/5027))
+
+The router now supports telemetry selectors that take into account the occurrence of errors. This capability enables you to create metrics, events, or span attributes that contain error messages.
+
+For example, you can create a counter for the number of timed-out requests for subgraphs:
+
+
+```yaml
+telemetry:
+ instrumentation:
+ instruments:
+ subgraph:
+ requests.timeout:
+ value: unit
+ type: counter
+ unit: request
+ description: "subgraph requests containing subgraph timeout"
+ attributes:
+ subgraph.name: true
+ condition:
+ eq:
+ - "request timed out"
+ - error: reason
+```
+
+The router also can now compute new attributes upon receiving a new event in a supergraph response. With this capability, you can fetch data directly from the supergraph response body:
+
+```yaml
+telemetry:
+ instrumentation:
+ instruments:
+ acme.request.on_graphql_error:
+ value: event_unit
+ type: counter
+ unit: error
+ description: my description
+ condition:
+ eq:
+ - MY_ERROR_CODE
+ - response_errors: "$.[0].extensions.code"
+ attributes:
+ response_errors:
+ response_errors: "$.*"
+```
+
+By [@bnjjj](https://github.com/bnjjj) in https://github.com/apollographql/router/pull/5022
+
+### Add support for `status_code` response to Rhai ([Issue #5042](https://github.com/apollographql/router/issues/5042))
+
+The router now supports `response.status_code` on the `Response` interface in Rhai.
+
+Examples using the response status code:
+
+- Converting a response status code to a string:
+
+```rhai
+if response.status_code.to_string() == "200" {
+ print(`ok`);
+}
+```
+
+- Converting a response status code to a number:
+
+```rhai
+if parse_int(response.status_code.to_string()) == 200 {
+ print(`ok`);
+}
+```
+
+By [@bnjjj](https://github.com/bnjjj) in https://github.com/apollographql/router/pull/5045
+
+### Add gt and lt operators for telemetry conditions ([PR #5048](https://github.com/apollographql/router/pull/5048))
+
+The router supports greater than (`gt`) and less than (`lt`) operators for telemetry conditions. Similar to the `eq` operator, the configuration for both `gt` and `lt` takes two arguments as a list. The `gt` operator checks that the first argument is greater than the second, and the `lt` operator checks that the first argument is less than the second. Other conditions such as `gte`, `lte`, and `range` can be made from combinations of `gt`, `lt`, `eq`, and `all`.
+
+By [@tninesling](https://github.com/tninesling) in https://github.com/apollographql/router/pull/5048
+
+### Expose busy timer APIs ([PR #4989](https://github.com/apollographql/router/pull/4989))
+
+The router supports public APIs that native plugins can use to control when the router's busy timer is run.
+
+The router's busy timer measures the time spent working on a request outside of waiting for external calls, like coprocessors and subgraph calls. It includes the time spent waiting for other concurrent requests to be handled (the wait time in the executor) to show the actual router overhead when handling requests.
+
+The public methods are `Context::enter_active_request` and `Context::busy_time`. The result is reported in the `apollo_router_processing_time` metric
+
+For details on using the APIs, see the documentation for [`enter_active_request`](https://www.apollographql.com/docs/router/customizations/native#enter_active_request).
+
+By [@Geal](https://github.com/Geal) in https://github.com/apollographql/router/pull/4989
+
+## 🐛 Fixes
+
+### Reduce JSON schema size and Router memory footprint ([PR #5061](https://github.com/apollographql/router/pull/5061))
+
+As we add more features to the Router the size of the JSON schema for the router configuration file continutes to grow. In particular, adding [conditionals to telemetry](https://github.com/apollographql/router/pull/4987) in v1.46.0 significantly increased this size of the schema. This has a noticeable impact on initial memory footprint, although it does not impact service of requests.
+
+The JSON schema for the router configuration file has been optimized from approximately 100k lines down to just over 7k.
+
+This reduces the startup time of the Router and a smaller schema is more friendly for code editors.
+
+By [@BrynCooke](https://github.com/BrynCooke) in https://github.com/apollographql/router/pull/5061
+
+### Prevent query plan cache collision when planning options change ([Issue #5093](https://github.com/apollographql/router/issues/5093))
+
+The router's hashing algorithm has been updated to prevent cache collisions when the router's configuration changes.
+
+> [!IMPORTANT]
+> If you have enabled [Distributed query plan caching](https://www.apollographql.com/docs/router/configuration/distributed-caching/#distributed-query-plan-caching), this release changes the hashing algorithm used for the cache keys. On account of this, you should anticipate additional cache regeneration cost when updating between these versions while the new hashing algorithm comes into service.
+
+The router supports multiple options that affect the generated query plans, including:
+* `defer_support`
+* `generate_query_fragments`
+* `experimental_reuse_query_fragments`
+* `experimental_type_conditioned_fetching`
+* `experimental_query_planner_mode`
+
+If distributed query plan caching is enabled, changing any of these options results in different query plans being generated and cached.
+
+This could be problematic in the following scenarios:
+
+1. The router configuration changes and a query plan is loaded from cache which is incompatible with the new configuration.
+2. Routers with different configurations share the same cache, which causes them to cache and load incompatible query plans.
+
+To prevent these from happening, the router now creates a hash for the entire query planner configuration and includes it in the cache key.
+
+By [@Geal](https://github.com/Geal) in https://github.com/apollographql/router/pull/5100
+
+### 5xx internal server error responses returned as GraphQL structured errors ([PR #5159](https://github.com/apollographql/router/pull/5159))
+
+Previously, the router returned internal server errors (5xx class) as plaintext to clients. Now in this release, the router returns these 5xx errors as structured GraphQL (for example, `{"errors": [...]}`).
+
+Internal server errors are returned upon unexpected or unrecoverable disruptions to the GraphQL request lifecycle execution. When these occur, the underlying error messages are logged at an `ERROR` level to the router's logs.
+By [@BrynCooke](https://github.com/BrynCooke) in https://github.com/apollographql/router/pull/5159
+
+### Custom telemetry events not created when logging is disabled ([PR #5165](https://github.com/apollographql/router/pull/5165))
+
+The router has been fixed to not create custom telemetry events when the log level is set to `off`.
+
+An example configuration with `level` set to `off` for a custom event:
+
+```yaml
+telemetry:
+ instrumentation:
+ events:
+ router:
+ # Standard events
+ request: info
+ response: info
+ error: info
+
+ # Custom events
+ my.disabled_request_event:
+ message: "my event message"
+ level: off # Disabled because we set the level to off
+ on: request
+ attributes:
+ http.request.body.size: true
+```
+
+By [@bnjjj](https://github.com/bnjjj) in https://github.com/apollographql/router/pull/5165
+
+### Ensure that batch entry contexts are correctly preserved ([PR #5162](https://github.com/apollographql/router/pull/5162))
+
+Previously, the router didn't use contexts correctly when processing batches. A representative context was chosen (the first item in a batch of items) and used to provide context functionality for all the generated responses.
+
+The router now correctly preserves request contexts and uses them during response creation.
+
+By [@garypen](https://github.com/garypen) in https://github.com/apollographql/router/pull/5162
+
+### Validate enum values in input variables ([Issue #4633](https://github.com/apollographql/router/issues/4633))
+
+The router now validates enum values provided in JSON variables. Invalid enum values result in `GRAPHQL_VALIDATION_FAILED` errors.
+
+By [@Geal](https://github.com/Geal) in https://github.com/apollographql/router/pull/4753
+
+### Strip dashes from `trace_id` in `CustomTraceIdPropagator` ([Issue #4892](https://github.com/apollographql/router/issues/4892))
+
+
+The router now strips dashes from trace IDs to ensure conformance with OpenTelemetry.
+
+In OpenTelemetry, trace IDs are 128-bit values represented as hex strings without dashes, and they're based on W3C's trace ID format.
+
+This has been applied within the router to `trace_id` in `CustomTraceIdPropagator`.
+
+Note, if raw trace IDs from headers are represented by uuid4 and contain dashes, the dashes should be stripped so that the raw trace ID value can be parsed into a valid `trace_id`.
+
+
+By [@kindermax](https://github.com/kindermax) in https://github.com/apollographql/router/pull/5071
+
+
+
# [1.46.0] - 2024-05-07
## 🚀 Features
diff --git a/Cargo.lock b/Cargo.lock
index b000800666..ee02ef9533 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -220,23 +220,35 @@ dependencies = [
[[package]]
name = "apollo-federation"
-version = "0.0.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e9fc457f3e836a60ea3d4e1a25a8b42c5c62ddf13a2131c194d94f752c7a1475"
+version = "1.47.0-rc.0"
dependencies = [
"apollo-compiler",
"derive_more",
+ "hex",
"indexmap 2.2.3",
+ "insta",
"lazy_static",
+ "multimap 0.10.0",
"petgraph",
"serde_json_bytes",
+ "sha1",
"strum 0.26.2",
"strum_macros 0.26.1",
+ "tempfile",
"thiserror",
"time",
"url",
]
+[[package]]
+name = "apollo-federation-cli"
+version = "0.1.0"
+dependencies = [
+ "apollo-compiler",
+ "apollo-federation",
+ "clap",
+]
+
[[package]]
name = "apollo-parser"
version = "0.7.7"
@@ -250,7 +262,7 @@ dependencies = [
[[package]]
name = "apollo-router"
-version = "1.46.0"
+version = "1.47.0"
dependencies = [
"access-json",
"anyhow",
@@ -412,7 +424,7 @@ dependencies = [
[[package]]
name = "apollo-router-benchmarks"
-version = "1.46.0"
+version = "1.47.0"
dependencies = [
"apollo-parser",
"apollo-router",
@@ -428,7 +440,7 @@ dependencies = [
[[package]]
name = "apollo-router-scaffold"
-version = "1.46.0"
+version = "1.47.0"
dependencies = [
"anyhow",
"cargo-scaffold",
@@ -3559,9 +3571,9 @@ dependencies = [
[[package]]
name = "insta"
-version = "1.35.1"
+version = "1.38.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7c985c1bef99cf13c58fade470483d81a2bfe846ebde60ed28cc2dddec2df9e2"
+checksum = "3eab73f58e59ca6526037208f0e98851159ec1633cf17b6cd2e1f2c3fd5d53cc"
dependencies = [
"console",
"lazy_static",
@@ -3570,7 +3582,6 @@ dependencies = [
"pest_derive",
"serde",
"similar",
- "yaml-rust",
]
[[package]]
@@ -4176,6 +4187,15 @@ dependencies = [
"serde",
]
+[[package]]
+name = "multimap"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03"
+dependencies = [
+ "serde",
+]
+
[[package]]
name = "nom"
version = "7.1.3"
diff --git a/Cargo.toml b/Cargo.toml
index 316fa6236a..c4d946f031 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,10 +1,12 @@
[workspace]
resolver = "2"
-default-members = ["apollo-router"]
+default-members = ["apollo-router", "apollo-federation"]
members = [
"apollo-router",
"apollo-router-benchmarks",
"apollo-router-scaffold",
+ "apollo-federation",
+ "apollo-federation/cli",
"examples/add-timestamp-header/rhai",
"examples/async-auth/rust",
"examples/cache-control/rhai",
@@ -49,7 +51,9 @@ apollo-compiler = "=1.0.0-beta.16"
apollo-parser = "0.7.6"
apollo-smith = { version = "0.5.0", features = ["parser-impl"] }
async-trait = "0.1.77"
+hex = { version = "0.4.3", features = ["serde"] }
http = "0.2.11"
+insta = { version = "1.38.0", features = ["json", "redactions", "yaml"] }
once_cell = "1.19.0"
reqwest = { version = "0.11.24", default-features = false, features = [
"rustls-tls",
@@ -66,5 +70,7 @@ serde_json = { version = "1.0.114", features = [
"float_roundtrip",
] }
serde_json_bytes = { version = "0.2.2", features = ["preserve_order"] }
+sha1 = "0.10.6"
+tempfile = "3.10.0"
tokio = { version = "1.36.0", features = ["full"] }
tower = { version = "0.4.13", features = ["full"] }
diff --git a/apollo-federation/.cargo/config.toml b/apollo-federation/.cargo/config.toml
new file mode 100644
index 0000000000..92fc0f1923
--- /dev/null
+++ b/apollo-federation/.cargo/config.toml
@@ -0,0 +1,6 @@
+[alias]
+cli = "run -p apollo-federation-cli --"
+
+# circle seems to install cargo packages via ssh:// rather than https://
+[net]
+git-fetch-with-cli = true
diff --git a/apollo-federation/.github/renovate.json5 b/apollo-federation/.github/renovate.json5
new file mode 100644
index 0000000000..7190a60b64
--- /dev/null
+++ b/apollo-federation/.github/renovate.json5
@@ -0,0 +1,3 @@
+{
+ "$schema": "https://docs.renovatebot.com/renovate-schema.json"
+}
diff --git a/apollo-federation/.gitignore b/apollo-federation/.gitignore
new file mode 100644
index 0000000000..357cc32340
--- /dev/null
+++ b/apollo-federation/.gitignore
@@ -0,0 +1,17 @@
+# idea
+.idea
+
+# Generated by Cargo
+# will have compiled files and executables
+debug/
+target/
+
+# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
+# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
+Cargo.lock
+
+# These are backup files generated by rustfmt
+**/*.rs.bk
+
+# MSVC Windows builds of rustc generate these, which store debugging information
+*.pdb
diff --git a/apollo-federation/CHANGELOG.md b/apollo-federation/CHANGELOG.md
new file mode 100644
index 0000000000..20df580ef7
--- /dev/null
+++ b/apollo-federation/CHANGELOG.md
@@ -0,0 +1,107 @@
+# Changelog
+
+All notable changes to `apollo-federation` will be documented in this file.
+
+This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+
+
+# [0.0.11](https://crates.io/crates/apollo-federation/0.0.11) - 2024-04-12
+
+## Fixes
+- Forbid aliases in `@requires(fields:)` / `@key(fields:)` argument, by [duckki] in [pull/251]
+
+## Features
+- Expose subgraphs schemas to crate consumers, by [SimonSapin] in [pull/257]
+
+## Maintenance
+- Update `apollo-compiler`, by [goto-bus-stop]
+
+[duckki]: https://github.com/duckki
+[goto-bus-stop]: https://github.com/goto-bus-stop
+[SimonSapin]: https://github.com/SimonSapin
+[pull/251]: https://github.com/apollographql/federation-next/pull/251
+[pull/257]: https://github.com/apollographql/federation-next/pull/257
+
+# [0.0.10](https://crates.io/crates/apollo-federation/0.0.10) - 2024-04-09
+
+## Features
+- Query plan changes for initial router integration, by [SimonSapin] in [pull/240]
+- Mark join/v0.4 spec as supported for non-query planning purpose, by [SimonSapin] in [pull/233], [pull/237]
+- Continued work on core query planning implementation, by [duckki], [SimonSapin], [TylerBloom]
+
+## Maintenance
+- Update `apollo-compiler`, by [goto-bus-stop] in [pull/253]
+
+[duckki]: https://github.com/duckki
+[goto-bus-stop]: https://github.com/goto-bus-stop
+[SimonSapin]: https://github.com/SimonSapin
+[TylerBloom]: https://github.com/TylerBloom
+[pull/233]: https://github.com/apollographql/federation-next/pull/233
+[pull/237]: https://github.com/apollographql/federation-next/pull/237
+[pull/240]: https://github.com/apollographql/federation-next/pull/240
+[pull/253]: https://github.com/apollographql/federation-next/pull/253
+
+# [0.0.9](https://crates.io/crates/apollo-federation/0.0.9) - 2024-03-20
+
+## Features
+- Continued work on core query planning implementation, by [goto-bus-stop] in [pull/229]
+
+## Maintenance
+- Update `apollo-compiler`, by [goto-bus-stop] in [pull/230]
+
+[goto-bus-stop]: https://github.com/goto-bus-stop
+[pull/229]: https://github.com/apollographql/federation-next/pull/229
+[pull/230]: https://github.com/apollographql/federation-next/pull/230
+
+# [0.0.8](https://crates.io/crates/apollo-federation/0.0.8) - 2024-03-06
+
+## Features
+- Support legacy `@core` link syntax, by [goto-bus-stop] in [pull/224]
+ This is not meant to be a long term feature, `@core()` is not intended
+ to be supported in most of the codebase.
+- Continued work on core query planning implementation, by [SimonSapin], [goto-bus-stop] in [pull/172], [pull/175]
+
+## Maintenance
+- `@link(url: String!)` argument is non-null, by [SimonSapin] in [pull/220]
+- Enable operation normalization tests using `@defer`, by [goto-bus-stop] in [pull/224]
+
+[SimonSapin]: https://github.com/SimonSapin
+[goto-bus-stop]: https://github.com/goto-bus-stop
+[pull/172]: https://github.com/apollographql/federation-next/pull/172
+[pull/175]: https://github.com/apollographql/federation-next/pull/175
+[pull/220]: https://github.com/apollographql/federation-next/pull/220
+[pull/223]: https://github.com/apollographql/federation-next/pull/223
+[pull/224]: https://github.com/apollographql/federation-next/pull/224
+
+# [0.0.7](https://crates.io/crates/apollo-federation/0.0.7) - 2024-02-22
+
+## Features
+- Continued work on core query planning implementation, by [SimonSapin] in [pull/121]
+
+## Fixes
+- Fix `@defer` directive definition in API schema generation, by [goto-bus-stop] in [pull/221]
+
+[SimonSapin]: https://github.com/SimonSapin
+[goto-bus-stop]: https://github.com/goto-bus-stop
+[pull/121]: https://github.com/apollographql/federation-next/pull/121
+[pull/221]: https://github.com/apollographql/federation-next/pull/221
+
+# [0.0.3](https://crates.io/crates/apollo-federation/0.0.3) - 2023-11-08
+
+## Features
+- Extracting subgraph information from a supergraph for the purposes of query planning by [sachindshinde] in [pull/56]
+
+[sachindshinde]: https://github.com/sachindshinde
+[pull/56]: https://github.com/apollographql/federation-next/pull/56
diff --git a/apollo-federation/Cargo.toml b/apollo-federation/Cargo.toml
new file mode 100644
index 0000000000..8cadb8b527
--- /dev/null
+++ b/apollo-federation/Cargo.toml
@@ -0,0 +1,35 @@
+[package]
+name = "apollo-federation"
+version = "1.47.0-rc.0"
+authors = ["The Apollo GraphQL Contributors"]
+edition = "2021"
+description = "Apollo Federation"
+documentation = "https://docs.rs/apollo-federation"
+repository = "https://github.com/apollographql/router"
+license = "Elastic-2.0"
+autotests = false # Integration tests are modules of tests/main.rs
+
+[dependencies]
+apollo-compiler.workspace = true
+time = { version = "0.3.34", default-features = false, features = [
+ "local-offset",
+] }
+derive_more = "0.99.17"
+indexmap = "2.1.0"
+lazy_static = "1.4.0"
+multimap = "0.10.0"
+petgraph = "0.6.4"
+serde_json_bytes.workspace = true
+strum = "0.26.0"
+strum_macros = "0.26.0"
+thiserror = "1.0"
+url = "2"
+
+[dev-dependencies]
+hex.workspace = true
+insta.workspace = true
+sha1.workspace = true
+tempfile.workspace = true
+
+[[test]]
+name = "main"
diff --git a/apollo-federation/LICENSE b/apollo-federation/LICENSE
new file mode 100644
index 0000000000..ae8dcbf1f2
--- /dev/null
+++ b/apollo-federation/LICENSE
@@ -0,0 +1,99 @@
+Copyright 2021 Apollo Graph, Inc.
+
+Source code in this repository is covered by (i) the Elastic License 2.0 or (ii) an MIT compatible license, in each case, as designated by a licensing file in a subdirectory or file header. The default throughout the repository is a license under the Elastic License 2.0, unless a file header or a licensing file in a subdirectory specifies another license.
+
+--------------------------------------------------------------------------------
+
+Elastic License 2.0
+
+## Acceptance
+
+By using the software, you agree to all of the terms and conditions below.
+
+## Copyright License
+
+The licensor grants you a non-exclusive, royalty-free, worldwide,
+non-sublicensable, non-transferable license to use, copy, distribute, make
+available, and prepare derivative works of the software, in each case subject to
+the limitations and conditions below.
+
+## Limitations
+
+You may not provide the software to third parties as a hosted or managed
+service, where the service provides users with access to any substantial set of
+the features or functionality of the software.
+
+You may not move, change, disable, or circumvent the license key functionality
+in the software, and you may not remove or obscure any functionality in the
+software that is protected by the license key.
+
+You may not alter, remove, or obscure any licensing, copyright, or other notices
+of the licensor in the software. Any use of the licensor’s trademarks is subject
+to applicable law.
+
+## Patents
+
+The licensor grants you a license, under any patent claims the licensor can
+license, or becomes able to license, to make, have made, use, sell, offer for
+sale, import and have imported the software, in each case subject to the
+limitations and conditions in this license. This license does not cover any
+patent claims that you cause to be infringed by modifications or additions to
+the software. If you or your company make any written claim that the software
+infringes or contributes to infringement of any patent, your patent license for
+the software granted under these terms ends immediately. If your company makes
+such a claim, your patent license ends immediately for work on behalf of your
+company.
+
+## Notices
+
+You must ensure that anyone who gets a copy of any part of the software from you
+also gets a copy of these terms.
+
+If you modify the software, you must include in any modified copies of the
+software prominent notices stating that you have modified the software.
+
+## No Other Rights
+
+These terms do not imply any licenses other than those expressly granted in
+these terms.
+
+## Termination
+
+If you use the software in violation of these terms, such use is not licensed,
+and your licenses will automatically terminate. If the licensor provides you
+with a notice of your violation, and you cease all violation of this license no
+later than 30 days after you receive that notice, your licenses will be
+reinstated retroactively. However, if you violate these terms after such
+reinstatement, any additional violation of these terms will cause your licenses
+to terminate automatically and permanently.
+
+## No Liability
+
+*As far as the law allows, the software comes as is, without any warranty or
+condition, and the licensor will not be liable to you for any damages arising
+out of these terms or the use or nature of the software, under any kind of
+legal claim.*
+
+## Definitions
+
+The **licensor** is the entity offering these terms, and the **software** is the
+software the licensor makes available under these terms, including any portion
+of it.
+
+**you** refers to the individual or entity agreeing to these terms.
+
+**your company** is any legal entity, sole proprietorship, or other kind of
+organization that you work for, plus all organizations that have control over,
+are under the control of, or are under common control with that
+organization. **control** means ownership of substantially all the assets of an
+entity, or the power to direct its management and policies by vote, contract, or
+otherwise. Control can be direct or indirect.
+
+**your licenses** are all the licenses granted to you for the software under
+these terms.
+
+**use** means anything you do with the software requiring one of your licenses.
+
+**trademark** means trademarks, service marks, and similar rights.
+
+--------------------------------------------------------------------------------
\ No newline at end of file
diff --git a/apollo-federation/README.md b/apollo-federation/README.md
new file mode 100644
index 0000000000..0a506dcbe5
--- /dev/null
+++ b/apollo-federation/README.md
@@ -0,0 +1,34 @@
+
+
+[![Crates.io](https://img.shields.io/crates/v/apollo-federation.svg?style=flat-square)](https://crates.io/crates/apollo-federation)
+[![docs](https://img.shields.io/static/v1?label=docs&message=apollo-federation&color=blue&style=flat-square)](https://docs.rs/apollo-federation/)
+[![Join the community forum](https://img.shields.io/badge/join%20the%20community-forum-blueviolet)](https://community.apollographql.com)
+[![Join our Discord server](https://img.shields.io/discord/1022972389463687228.svg?color=7389D8&labelColor=6A7EC2&logo=discord&logoColor=ffffff&style=flat-square)](https://discord.gg/graphos)
+
+Apollo Federation
+-----------------------------
+Apollo Federation is an architecture for declaratively composing APIs into a unified graph. Each team can own their slice of the graph independently, empowering them to deliver autonomously and incrementally.
+
+Federation 2 is an evolution of the original Apollo Federation with an improved shared ownership model, enhanced type merging, and cleaner syntax for a smoother developer experience. It’s backwards compatible, requiring no major changes to your subgraphs.
+
+Checkout the [Federation 2 docs](https://www.apollographql.com/docs/federation) and [demo repo](https://github.com/apollographql/supergraph-demo-fed2) to take it for a spin and [let us know what you think](https://community.apollographql.com/t/announcing-apollo-federation-2/1821)!
+
+## Usage
+
+TODO
+
+### CLI tool
+
+`cargo fed --help`
+
+## Contributing
+
+TODO
+
+## Security
+
+For more info on how to contact the team for security issues, see our [Security Policy](https://github.com/apollographql/federation-next/security/policy).
+
+## License
+
+Source code in this repository is covered by the Elastic License 2.0. The default throughout the repository is a license under the Elastic License 2.0, unless a file header or a license file in a subdirectory specifies another license. [See the LICENSE](./LICENSE) for the full license text.
diff --git a/apollo-federation/cli/Cargo.toml b/apollo-federation/cli/Cargo.toml
new file mode 100644
index 0000000000..b64cc7a03c
--- /dev/null
+++ b/apollo-federation/cli/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "apollo-federation-cli"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+apollo-compiler.workspace = true
+apollo-federation = { path = ".." }
+clap = { version = "4.5.1", features = ["derive"] }
diff --git a/apollo-federation/cli/src/main.rs b/apollo-federation/cli/src/main.rs
new file mode 100644
index 0000000000..415b7efb7b
--- /dev/null
+++ b/apollo-federation/cli/src/main.rs
@@ -0,0 +1,212 @@
+use std::fs;
+use std::io;
+use std::path::Path;
+use std::path::PathBuf;
+use std::process::ExitCode;
+
+use apollo_compiler::ExecutableDocument;
+use apollo_federation::error::FederationError;
+use apollo_federation::error::SingleFederationError;
+use apollo_federation::query_graph;
+use apollo_federation::query_plan::query_planner::QueryPlanner;
+use apollo_federation::query_plan::query_planner::QueryPlannerConfig;
+use apollo_federation::subgraph;
+use clap::Parser;
+
+/// CLI arguments. See
+#[derive(Parser)]
+struct Args {
+ #[command(subcommand)]
+ command: Command,
+}
+
+#[derive(clap::Subcommand)]
+enum Command {
+ /// Converts a supergraph schema to the corresponding API schema
+ Api {
+ /// Path(s) to one supergraph schema file, `-` for stdin or multiple subgraph schemas.
+ schemas: Vec,
+ },
+ /// Outputs the query graph from a supergraph schema or subgraph schemas
+ QueryGraph {
+ /// Path(s) to one supergraph schema file, `-` for stdin or multiple subgraph schemas.
+ schemas: Vec,
+ },
+ /// Outputs the federated query graph from a supergraph schema or subgraph schemas
+ FederatedGraph {
+ /// Path(s) to one supergraph schema file, `-` for stdin or multiple subgraph schemas.
+ schemas: Vec,
+ },
+ /// Outputs the formatted query plan for the given query and schema
+ Plan {
+ query: PathBuf,
+ /// Path(s) to one supergraph schema file, `-` for stdin or multiple subgraph schemas.
+ schemas: Vec,
+ },
+ /// Validate one supergraph schema file or multiple subgraph schemas
+ Validate {
+ /// Path(s) to one supergraph schema file, `-` for stdin or multiple subgraph schemas.
+ schemas: Vec,
+ },
+ /// Compose a supergraph schema from multiple subgraph schemas
+ Compose {
+ /// Path(s) to subgraph schemas.
+ schemas: Vec,
+ },
+ /// Extract subgraph schemas from a supergraph schema to stdout (or in a directory if specified)
+ Extract {
+ /// The path to the supergraph schema file, or `-` for stdin
+ supergraph_schema: PathBuf,
+ /// The output directory for the extracted subgraph schemas
+ destination_dir: Option,
+ },
+}
+
+fn main() -> ExitCode {
+ let args = Args::parse();
+ let result = match args.command {
+ Command::Api { schemas } => to_api_schema(&schemas),
+ Command::QueryGraph { schemas } => dot_query_graph(&schemas),
+ Command::FederatedGraph { schemas } => dot_federated_graph(&schemas),
+ Command::Plan { query, schemas } => plan(&query, &schemas),
+ Command::Validate { schemas } => cmd_validate(&schemas),
+ Command::Compose { schemas } => cmd_compose(&schemas),
+ Command::Extract {
+ supergraph_schema,
+ destination_dir,
+ } => cmd_extract(&supergraph_schema, destination_dir.as_ref()),
+ };
+ match result {
+ Err(error) => {
+ eprintln!("{error}");
+ ExitCode::FAILURE
+ }
+
+ Ok(_) => ExitCode::SUCCESS,
+ }
+}
+
+fn read_input(input_path: &Path) -> String {
+ if input_path == std::path::Path::new("-") {
+ io::read_to_string(io::stdin()).unwrap()
+ } else {
+ fs::read_to_string(input_path).unwrap()
+ }
+}
+
+fn to_api_schema(file_paths: &[PathBuf]) -> Result<(), FederationError> {
+ let supergraph = load_supergraph(file_paths)?;
+ let api_schema = supergraph.to_api_schema(apollo_federation::ApiSchemaOptions {
+ include_defer: true,
+ include_stream: false,
+ })?;
+ println!("{}", api_schema.schema());
+ Ok(())
+}
+
+/// Compose a supergraph from multiple subgraph files.
+fn compose_files(file_paths: &[PathBuf]) -> Result {
+ let schemas: Vec<_> = file_paths
+ .iter()
+ .map(|pathname| {
+ let doc_str = std::fs::read_to_string(pathname).unwrap();
+ let url = format!("file://{}", pathname.to_str().unwrap());
+ let basename = pathname.file_stem().unwrap().to_str().unwrap();
+ subgraph::Subgraph::parse_and_expand(basename, &url, &doc_str).unwrap()
+ })
+ .collect();
+ let supergraph = apollo_federation::Supergraph::compose(schemas.iter().collect()).unwrap();
+ Ok(supergraph)
+}
+
+fn load_supergraph_file(
+ file_path: &Path,
+) -> Result {
+ let doc_str = read_input(file_path);
+ apollo_federation::Supergraph::new(&doc_str)
+}
+
+/// Load either single supergraph schema file or compose one from multiple subgraph files.
+/// If the single file is "-", read from stdin.
+fn load_supergraph(
+ file_paths: &[PathBuf],
+) -> Result {
+ if file_paths.is_empty() {
+ panic!("Error: missing command arguments");
+ } else if file_paths.len() == 1 {
+ load_supergraph_file(&file_paths[0])
+ } else {
+ compose_files(file_paths)
+ }
+}
+
+fn dot_query_graph(file_paths: &[PathBuf]) -> Result<(), FederationError> {
+ let supergraph = load_supergraph(file_paths)?;
+ let name: &str = if file_paths.len() == 1 {
+ file_paths[0].file_stem().unwrap().to_str().unwrap()
+ } else {
+ "supergraph"
+ };
+ let query_graph =
+ query_graph::build_query_graph::build_query_graph(name.into(), supergraph.schema)?;
+ println!("{}", query_graph::output::to_dot(&query_graph));
+ Ok(())
+}
+
+fn dot_federated_graph(file_paths: &[PathBuf]) -> Result<(), FederationError> {
+ let supergraph = load_supergraph(file_paths)?;
+ let api_schema = supergraph.to_api_schema(Default::default())?;
+ let query_graph =
+ query_graph::build_federated_query_graph(supergraph.schema, api_schema, None, None)?;
+ println!("{}", query_graph::output::to_dot(&query_graph));
+ Ok(())
+}
+
+fn plan(query_path: &Path, schema_paths: &[PathBuf]) -> Result<(), FederationError> {
+ let query = read_input(query_path);
+ let supergraph = load_supergraph(schema_paths)?;
+ let query_doc =
+ ExecutableDocument::parse_and_validate(supergraph.schema.schema(), query, query_path)?;
+ // TODO: add CLI parameters for config as needed
+ let config = QueryPlannerConfig::default();
+ let planner = QueryPlanner::new(&supergraph, config)?;
+ print!("{}", planner.build_query_plan(&query_doc, None)?);
+ Ok(())
+}
+
+fn cmd_validate(file_paths: &[PathBuf]) -> Result<(), FederationError> {
+ load_supergraph(file_paths)?;
+ println!("[SUCCESS]");
+ Ok(())
+}
+
+fn cmd_compose(file_paths: &[PathBuf]) -> Result<(), FederationError> {
+ let supergraph = compose_files(file_paths)?;
+ println!("{}", supergraph.schema.schema());
+ Ok(())
+}
+
+fn cmd_extract(file_path: &Path, dest: Option<&PathBuf>) -> Result<(), FederationError> {
+ let supergraph = load_supergraph_file(file_path)?;
+ let subgraphs = supergraph.extract_subgraphs()?;
+ if let Some(dest) = dest {
+ fs::create_dir_all(dest).map_err(|_| SingleFederationError::Internal {
+ message: "Error: directory creation failed".into(),
+ })?;
+ for (name, subgraph) in subgraphs {
+ let subgraph_path = dest.join(format!("{}.graphql", name));
+ fs::write(subgraph_path, subgraph.schema.schema().to_string()).map_err(|_| {
+ SingleFederationError::Internal {
+ message: "Error: file output failed".into(),
+ }
+ })?;
+ }
+ } else {
+ for (name, subgraph) in subgraphs {
+ println!("[Subgraph `{}`]", name);
+ println!("{}", subgraph.schema.schema());
+ println!(); // newline
+ }
+ }
+ Ok(())
+}
diff --git a/apollo-federation/examples/api_schema.rs b/apollo-federation/examples/api_schema.rs
new file mode 100644
index 0000000000..9cda97afc6
--- /dev/null
+++ b/apollo-federation/examples/api_schema.rs
@@ -0,0 +1,26 @@
+use std::process::ExitCode;
+
+use apollo_compiler::Schema;
+use apollo_federation::Supergraph;
+
+fn main() -> ExitCode {
+ let (source, name) = match std::env::args().nth(1) {
+ Some(filename) => (std::fs::read_to_string(&filename).unwrap(), filename),
+ None => {
+ return ExitCode::FAILURE;
+ }
+ };
+
+ let schema = Schema::parse_and_validate(source, name).unwrap();
+ let supergraph = Supergraph::from_schema(schema).unwrap();
+
+ match supergraph.to_api_schema(Default::default()) {
+ Ok(result) => println!("{}", result.schema()),
+ Err(error) => {
+ eprintln!("{error}");
+ return ExitCode::FAILURE;
+ }
+ }
+
+ ExitCode::SUCCESS
+}
diff --git a/apollo-federation/src/api_schema.rs b/apollo-federation/src/api_schema.rs
new file mode 100644
index 0000000000..16626ef741
--- /dev/null
+++ b/apollo-federation/src/api_schema.rs
@@ -0,0 +1,226 @@
+//! Implements API schema generation.
+use apollo_compiler::name;
+use apollo_compiler::schema::DirectiveDefinition;
+use apollo_compiler::schema::DirectiveLocation;
+use apollo_compiler::schema::InputValueDefinition;
+use apollo_compiler::ty;
+use apollo_compiler::Node;
+
+use crate::error::FederationError;
+use crate::link::inaccessible_spec_definition::InaccessibleSpecDefinition;
+use crate::schema::position;
+use crate::schema::FederationSchema;
+use crate::schema::ValidFederationSchema;
+
+/// Remove types and directives imported by `@link`.
+fn remove_core_feature_elements(schema: &mut FederationSchema) -> Result<(), FederationError> {
+ let Some(metadata) = schema.metadata() else {
+ return Ok(());
+ };
+
+ // First collect the things to be removed so we do not hold any immutable references
+ // to the schema while mutating it below.
+ let types_for_removal = schema
+ .get_types()
+ .filter(|position| metadata.source_link_of_type(position.type_name()).is_some())
+ .collect::>();
+
+ let directives_for_removal = schema
+ .get_directive_definitions()
+ .filter(|position| {
+ metadata
+ .source_link_of_directive(&position.directive_name)
+ .is_some()
+ })
+ .collect::>();
+
+ // First remove children of elements that need to be removed, so there won't be outgoing
+ // references from the type.
+ for position in &types_for_removal {
+ match position {
+ position::TypeDefinitionPosition::Object(position) => {
+ let object = position.get(schema.schema())?;
+ let remove_children = object
+ .fields
+ .keys()
+ .map(|field_name| position.field(field_name.clone()))
+ .collect::>();
+ for child in remove_children {
+ child.remove(schema)?;
+ }
+ }
+ position::TypeDefinitionPosition::Interface(position) => {
+ let interface = position.get(schema.schema())?;
+ let remove_children = interface
+ .fields
+ .keys()
+ .map(|field_name| position.field(field_name.clone()))
+ .collect::>();
+ for child in remove_children {
+ child.remove(schema)?;
+ }
+ }
+ position::TypeDefinitionPosition::InputObject(position) => {
+ let input_object = position.get(schema.schema())?;
+ let remove_children = input_object
+ .fields
+ .keys()
+ .map(|field_name| position.field(field_name.clone()))
+ .collect::>();
+ for child in remove_children {
+ child.remove(schema)?;
+ }
+ }
+ position::TypeDefinitionPosition::Enum(position) => {
+ let enum_ = position.get(schema.schema())?;
+ let remove_children = enum_
+ .values
+ .keys()
+ .map(|field_name| position.value(field_name.clone()))
+ .collect::>();
+ for child in remove_children {
+ child.remove(schema)?;
+ }
+ }
+ _ => {}
+ }
+ }
+
+ for position in &directives_for_removal {
+ position.remove(schema)?;
+ }
+
+ for position in &types_for_removal {
+ match position {
+ position::TypeDefinitionPosition::Object(position) => {
+ position.remove(schema)?;
+ }
+ position::TypeDefinitionPosition::Interface(position) => {
+ position.remove(schema)?;
+ }
+ position::TypeDefinitionPosition::InputObject(position) => {
+ position.remove(schema)?;
+ }
+ position::TypeDefinitionPosition::Enum(position) => {
+ position.remove(schema)?;
+ }
+ position::TypeDefinitionPosition::Scalar(position) => {
+ position.remove(schema)?;
+ }
+ position::TypeDefinitionPosition::Union(position) => {
+ position.remove(schema)?;
+ }
+ }
+ }
+
+ Ok(())
+}
+
+#[derive(Debug, Default, Clone)]
+pub struct ApiSchemaOptions {
+ pub include_defer: bool,
+ pub include_stream: bool,
+}
+
+pub fn to_api_schema(
+ schema: ValidFederationSchema,
+ options: ApiSchemaOptions,
+) -> Result {
+ // Create a whole new federation schema that we can mutate.
+ let mut api_schema = FederationSchema::new(schema.schema().clone().into_inner())?;
+
+ // As we compute the API schema of a supergraph, we want to ignore explicit definitions of `@defer` and `@stream` because
+ // those correspond to the merging of potential definitions from the subgraphs, but whether the supergraph API schema
+ // supports defer or not is unrelated to whether subgraphs support it.
+ if let Some(defer) = api_schema.get_directive_definition(&name!("defer")) {
+ defer.remove(&mut api_schema)?;
+ }
+ if let Some(stream) = api_schema.get_directive_definition(&name!("stream")) {
+ stream.remove(&mut api_schema)?;
+ }
+
+ if let Some(inaccessible_spec) = InaccessibleSpecDefinition::get_from_schema(&api_schema)? {
+ inaccessible_spec.validate_inaccessible(&api_schema)?;
+ inaccessible_spec.remove_inaccessible_elements(&mut api_schema)?;
+ }
+
+ remove_core_feature_elements(&mut api_schema)?;
+
+ let schema = api_schema.schema_mut();
+
+ if options.include_defer {
+ schema
+ .directive_definitions
+ .insert(name!("defer"), defer_definition());
+ }
+
+ if options.include_stream {
+ schema
+ .directive_definitions
+ .insert(name!("stream"), stream_definition());
+ }
+
+ crate::compat::make_print_schema_compatible(schema);
+
+ api_schema.validate()
+}
+
+fn defer_definition() -> Node {
+ Node::new(DirectiveDefinition {
+ description: None,
+ name: name!("defer"),
+ arguments: vec![
+ Node::new(InputValueDefinition {
+ description: None,
+ name: name!("label"),
+ ty: ty!(String).into(),
+ default_value: None,
+ directives: Default::default(),
+ }),
+ Node::new(InputValueDefinition {
+ description: None,
+ name: name!("if"),
+ ty: ty!(Boolean!).into(),
+ default_value: Some(true.into()),
+ directives: Default::default(),
+ }),
+ ],
+ repeatable: false,
+ locations: vec![
+ DirectiveLocation::FragmentSpread,
+ DirectiveLocation::InlineFragment,
+ ],
+ })
+}
+
+fn stream_definition() -> Node {
+ Node::new(DirectiveDefinition {
+ description: None,
+ name: name!("stream"),
+ arguments: vec![
+ Node::new(InputValueDefinition {
+ description: None,
+ name: name!("label"),
+ ty: ty!(String).into(),
+ default_value: None,
+ directives: Default::default(),
+ }),
+ Node::new(InputValueDefinition {
+ description: None,
+ name: name!("if"),
+ ty: ty!(Boolean!).into(),
+ default_value: Some(true.into()),
+ directives: Default::default(),
+ }),
+ Node::new(InputValueDefinition {
+ description: None,
+ name: name!("initialCount"),
+ ty: ty!(Int).into(),
+ default_value: Some(0.into()),
+ directives: Default::default(),
+ }),
+ ],
+ repeatable: false,
+ locations: vec![DirectiveLocation::Field],
+ })
+}
diff --git a/apollo-federation/src/compat.rs b/apollo-federation/src/compat.rs
new file mode 100644
index 0000000000..df899de5d8
--- /dev/null
+++ b/apollo-federation/src/compat.rs
@@ -0,0 +1,290 @@
+//! Functions for output compatibility between graphql-js and apollo-rs
+//!
+//! apollo-rs produces different SDL than graphql-js based tools. For example, it chooses to
+//! include directive applications by default where graphql-js does not support doing that
+//! at all.
+//!
+//! This module contains functions that modify an apollo-rs schema to produce the same output as a
+//! graphql-js schema would.
+
+use apollo_compiler::ast::Value;
+use apollo_compiler::schema::Directive;
+use apollo_compiler::schema::ExtendedType;
+use apollo_compiler::schema::InputValueDefinition;
+use apollo_compiler::schema::Name;
+use apollo_compiler::schema::Type;
+use apollo_compiler::Node;
+use apollo_compiler::Schema;
+use indexmap::IndexMap;
+
+/// Return true if a directive application is "semantic", meaning it's observable in introspection.
+fn is_semantic_directive_application(directive: &Directive) -> bool {
+ match directive.name.as_str() {
+ "specifiedBy" => true,
+ // For @deprecated, explicitly writing `reason: null` disables the directive,
+ // as `null` overrides the default string value.
+ "deprecated"
+ if directive
+ .argument_by_name("reason")
+ .is_some_and(|value| value.is_null()) =>
+ {
+ false
+ }
+ "deprecated" => true,
+ _ => false,
+ }
+}
+
+/// Remove `reason` argument from a `@deprecated` directive if it has the default value, just to match graphql-js output.
+fn standardize_deprecated(directive: &mut Directive) {
+ if directive.name == "deprecated"
+ && directive
+ .argument_by_name("reason")
+ .and_then(|value| value.as_str())
+ .is_some_and(|reason| reason == "No longer supported")
+ {
+ directive.arguments.clear();
+ }
+}
+
+/// Retain only semantic directives in a directive list from the high-level schema representation.
+fn retain_semantic_directives(directives: &mut apollo_compiler::schema::DirectiveList) {
+ directives
+ .0
+ .retain(|directive| is_semantic_directive_application(directive));
+
+ for directive in directives {
+ standardize_deprecated(directive.make_mut());
+ }
+}
+
+/// Retain only semantic directives in a directive list from the AST-level schema representation.
+fn retain_semantic_directives_ast(directives: &mut apollo_compiler::ast::DirectiveList) {
+ directives
+ .0
+ .retain(|directive| is_semantic_directive_application(directive));
+
+ for directive in directives {
+ standardize_deprecated(directive.make_mut());
+ }
+}
+
+/// Remove non-semantic directive applications from the schema representation.
+/// This only keeps directive applications that are observable in introspection.
+pub fn remove_non_semantic_directives(schema: &mut Schema) {
+ let root_definitions = schema.schema_definition.make_mut();
+ retain_semantic_directives(&mut root_definitions.directives);
+
+ for ty in schema.types.values_mut() {
+ match ty {
+ ExtendedType::Object(object) => {
+ let object = object.make_mut();
+ retain_semantic_directives(&mut object.directives);
+ for field in object.fields.values_mut() {
+ let field = field.make_mut();
+ retain_semantic_directives_ast(&mut field.directives);
+ for arg in &mut field.arguments {
+ let arg = arg.make_mut();
+ retain_semantic_directives_ast(&mut arg.directives);
+ }
+ }
+ }
+ ExtendedType::Interface(interface) => {
+ let interface = interface.make_mut();
+ retain_semantic_directives(&mut interface.directives);
+ for field in interface.fields.values_mut() {
+ let field = field.make_mut();
+ retain_semantic_directives_ast(&mut field.directives);
+ for arg in &mut field.arguments {
+ let arg = arg.make_mut();
+ retain_semantic_directives_ast(&mut arg.directives);
+ }
+ }
+ }
+ ExtendedType::InputObject(input_object) => {
+ let input_object = input_object.make_mut();
+ retain_semantic_directives(&mut input_object.directives);
+ for field in input_object.fields.values_mut() {
+ let field = field.make_mut();
+ retain_semantic_directives_ast(&mut field.directives);
+ }
+ }
+ ExtendedType::Union(union_) => {
+ let union_ = union_.make_mut();
+ retain_semantic_directives(&mut union_.directives);
+ }
+ ExtendedType::Scalar(scalar) => {
+ let scalar = scalar.make_mut();
+ retain_semantic_directives(&mut scalar.directives);
+ }
+ ExtendedType::Enum(enum_) => {
+ let enum_ = enum_.make_mut();
+ retain_semantic_directives(&mut enum_.directives);
+ for value in enum_.values.values_mut() {
+ let value = value.make_mut();
+ retain_semantic_directives_ast(&mut value.directives);
+ }
+ }
+ }
+ }
+
+ for directive in schema.directive_definitions.values_mut() {
+ let directive = directive.make_mut();
+ for arg in &mut directive.arguments {
+ let arg = arg.make_mut();
+ retain_semantic_directives_ast(&mut arg.directives);
+ }
+ }
+}
+
+// Just a boolean with a `?` operator
+type CoerceResult = Result<(), ()>;
+
+/// Recursively assign default values in input object values, mutating the value.
+/// If the default value is invalid, returns `Err(())`.
+fn coerce_value(
+ types: &IndexMap,
+ target: &mut Node,
+ ty: &Type,
+) -> CoerceResult {
+ match (target.make_mut(), types.get(ty.inner_named_type())) {
+ (Value::Object(object), Some(ExtendedType::InputObject(definition))) if ty.is_named() => {
+ for (field_name, field_definition) in definition.fields.iter() {
+ match object.iter_mut().find(|(key, _value)| key == field_name) {
+ Some((_name, value)) => {
+ coerce_value(types, value, &field_definition.ty)?;
+ }
+ None => {
+ if let Some(default_value) = &field_definition.default_value {
+ let mut value = default_value.clone();
+ // If the default value is an input object we may need to fill in
+ // its defaulted fields recursively.
+ coerce_value(types, &mut value, &field_definition.ty)?;
+ object.push((field_name.clone(), value));
+ } else if field_definition.is_required() {
+ return Err(());
+ }
+ }
+ }
+ }
+ }
+ (Value::List(list), Some(_)) if ty.is_list() => {
+ for element in list {
+ coerce_value(types, element, ty.item_type())?;
+ }
+ }
+ // Coerce single values (except null) to a list.
+ (
+ Value::Object(_)
+ | Value::String(_)
+ | Value::Enum(_)
+ | Value::Int(_)
+ | Value::Float(_)
+ | Value::Boolean(_),
+ Some(_),
+ ) if ty.is_list() => {
+ coerce_value(types, target, ty.item_type())?;
+ *target.make_mut() = Value::List(vec![target.clone()]);
+ }
+
+ // Accept null for any nullable type.
+ (Value::Null, _) if !ty.is_non_null() => {}
+
+ // Accept non-composite values if they match the type.
+ (Value::String(_), Some(ExtendedType::Scalar(scalar)))
+ if !scalar.is_built_in() || matches!(scalar.name.as_str(), "ID" | "String") => {}
+ (Value::Boolean(_), Some(ExtendedType::Scalar(scalar)))
+ if !scalar.is_built_in() || scalar.name == "Boolean" => {}
+ (Value::Int(_), Some(ExtendedType::Scalar(scalar)))
+ if !scalar.is_built_in() || matches!(scalar.name.as_str(), "ID" | "Int" | "Float") => {}
+ (Value::Float(_), Some(ExtendedType::Scalar(scalar)))
+ if !scalar.is_built_in() || scalar.name == "Float" => {}
+ // Custom scalars accept any value, even objects and lists.
+ (Value::Object(_), Some(ExtendedType::Scalar(scalar))) if !scalar.is_built_in() => {}
+ (Value::List(_), Some(ExtendedType::Scalar(scalar))) if !scalar.is_built_in() => {}
+ // Enums must match the type.
+ (Value::Enum(value), Some(ExtendedType::Enum(enum_)))
+ if enum_.values.contains_key(value) => {}
+
+ // Other types are totally invalid (and should ideally be rejected by validation).
+ _ => return Err(()),
+ }
+ Ok(())
+}
+
+/// Coerce default values in all the given arguments, mutating the arguments.
+/// If a default value is invalid, the whole default value is removed silently.
+fn coerce_arguments_default_values(
+ types: &IndexMap,
+ arguments: &mut Vec>,
+) {
+ for arg in arguments {
+ let arg = arg.make_mut();
+ let Some(default_value) = &mut arg.default_value else {
+ continue;
+ };
+
+ if coerce_value(types, default_value, &arg.ty).is_err() {
+ arg.default_value = None;
+ }
+ }
+}
+
+/// Do graphql-js-style input coercion on default values. Invalid default values are silently
+/// removed from the schema.
+///
+/// This is not what we would want to do for coercion in a real execution scenario, but it matches
+/// a behaviour in graphql-js so we can compare API schema results between federation-next and JS
+/// federation. We can consider removing this when we no longer rely on JS federation.
+pub fn coerce_schema_default_values(schema: &mut Schema) {
+ // Keep a copy of the types in the schema so we can mutate the schema while walking it.
+ let types = schema.types.clone();
+
+ for ty in schema.types.values_mut() {
+ match ty {
+ ExtendedType::Object(object) => {
+ let object = object.make_mut();
+ for field in object.fields.values_mut() {
+ let field = field.make_mut();
+ coerce_arguments_default_values(&types, &mut field.arguments);
+ }
+ }
+ ExtendedType::Interface(interface) => {
+ let interface = interface.make_mut();
+ for field in interface.fields.values_mut() {
+ let field = field.make_mut();
+ coerce_arguments_default_values(&types, &mut field.arguments);
+ }
+ }
+ ExtendedType::InputObject(input_object) => {
+ let input_object = input_object.make_mut();
+ for field in input_object.fields.values_mut() {
+ let field = field.make_mut();
+ let Some(default_value) = &mut field.default_value else {
+ continue;
+ };
+
+ if coerce_value(&types, default_value, &field.ty).is_err() {
+ field.default_value = None;
+ }
+ }
+ }
+ ExtendedType::Union(_) | ExtendedType::Scalar(_) | ExtendedType::Enum(_) => {
+ // Nothing to do
+ }
+ }
+ }
+
+ for directive in schema.directive_definitions.values_mut() {
+ let directive = directive.make_mut();
+ coerce_arguments_default_values(&types, &mut directive.arguments);
+ }
+}
+
+/// Applies default value coercion and removes non-semantic directives so that
+/// the apollo-rs serialized output of the schema matches the result of
+/// `printSchema(buildSchema()` in graphql-js.
+pub fn make_print_schema_compatible(schema: &mut Schema) {
+ remove_non_semantic_directives(schema);
+ coerce_schema_default_values(schema);
+}
diff --git a/apollo-federation/src/error/mod.rs b/apollo-federation/src/error/mod.rs
new file mode 100644
index 0000000000..881b2b395b
--- /dev/null
+++ b/apollo-federation/src/error/mod.rs
@@ -0,0 +1,1321 @@
+use std::cmp::Ordering;
+use std::fmt::Display;
+use std::fmt::Formatter;
+use std::fmt::Write;
+
+use apollo_compiler::ast::InvalidNameError;
+use apollo_compiler::validation::DiagnosticList;
+use apollo_compiler::validation::WithErrors;
+use lazy_static::lazy_static;
+
+use crate::subgraph::spec::FederationSpecError;
+
+// What we really needed here was the string representations in enum form, this isn't meant to
+// replace AST components.
+#[derive(Clone, Debug, strum_macros::Display)]
+enum SchemaRootKind {
+ #[strum(to_string = "query")]
+ Query,
+ #[strum(to_string = "mutation")]
+ Mutation,
+ #[strum(to_string = "subscription")]
+ Subscription,
+}
+
+impl From for String {
+ fn from(value: SchemaRootKind) -> Self {
+ value.to_string()
+ }
+}
+
+#[derive(Debug, Clone, thiserror::Error)]
+pub enum SingleFederationError {
+ #[error(
+ "An internal error has occurred, please report this bug to Apollo.\n\nDetails: {message}"
+ )]
+ Internal { message: String },
+ #[error("{message}")]
+ InvalidGraphQL { message: String },
+ #[error("{message}")]
+ DirectiveDefinitionInvalid { message: String },
+ #[error("{message}")]
+ TypeDefinitionInvalid { message: String },
+ #[error("{message}")]
+ UnsupportedLinkedFeature { message: String },
+ #[error("{message}")]
+ UnknownFederationLinkVersion { message: String },
+ #[error("{message}")]
+ UnknownLinkVersion { message: String },
+ #[error("{message}")]
+ KeyFieldsHasArgs { message: String },
+ #[error("{message}")]
+ ProvidesFieldsHasArgs { message: String },
+ #[error("{message}")]
+ ProvidesFieldsMissingExternal { message: String },
+ #[error("{message}")]
+ RequiresFieldsMissingExternal { message: String },
+ #[error("{message}")]
+ KeyUnsupportedOnInterface { message: String },
+ #[error("{message}")]
+ ProvidesUnsupportedOnInterface { message: String },
+ #[error("{message}")]
+ RequiresUnsupportedOnInterface { message: String },
+ #[error("{message}")]
+ KeyDirectiveInFieldsArgs { message: String },
+ #[error("{message}")]
+ ProvidesDirectiveInFieldsArgs { message: String },
+ #[error("{message}")]
+ RequiresDirectiveInFieldsArgs { message: String },
+ #[error("{message}")]
+ ExternalUnused { message: String },
+ #[error("{message}")]
+ TypeWithOnlyUnusedExternal { message: String },
+ #[error("{message}")]
+ ProvidesOnNonObjectField { message: String },
+ #[error("{message}")]
+ KeyInvalidFieldsType { message: String },
+ #[error("{message}")]
+ ProvidesInvalidFieldsType { message: String },
+ #[error("{message}")]
+ RequiresInvalidFieldsType { message: String },
+ #[error("{message}")]
+ KeyInvalidFields { message: String },
+ #[error("{message}")]
+ ProvidesInvalidFields { message: String },
+ #[error("{message}")]
+ RequiresInvalidFields { message: String },
+ #[error("{message}")]
+ KeyFieldsSelectInvalidType { message: String },
+ #[error("{message}")]
+ RootQueryUsed { message: String },
+ #[error("{message}")]
+ RootMutationUsed { message: String },
+ #[error("{message}")]
+ RootSubscriptionUsed { message: String },
+ #[error("{message}")]
+ InvalidSubgraphName { message: String },
+ #[error("{message}")]
+ NoQueries { message: String },
+ #[error("{message}")]
+ InterfaceFieldNoImplem { message: String },
+ #[error("{message}")]
+ TypeKindMismatch { message: String },
+ #[error("{message}")]
+ ExternalTypeMismatch { message: String },
+ #[error("{message}")]
+ ExternalCollisionWithAnotherDirective { message: String },
+ #[error("{message}")]
+ ExternalArgumentMissing { message: String },
+ #[error("{message}")]
+ ExternalArgumentTypeMismatch { message: String },
+ #[error("{message}")]
+ ExternalArgumentDefaultMismatch { message: String },
+ #[error("{message}")]
+ ExternalOnInterface { message: String },
+ #[error("{message}")]
+ MergedDirectiveApplicationOnExternal { message: String },
+ #[error("{message}")]
+ FieldTypeMismatch { message: String },
+ #[error("{message}")]
+ FieldArgumentTypeMismatch { message: String },
+ #[error("{message}")]
+ InputFieldDefaultMismatch { message: String },
+ #[error("{message}")]
+ FieldArgumentDefaultMismatch { message: String },
+ #[error("{message}")]
+ ExtensionWithNoBase { message: String },
+ #[error("{message}")]
+ ExternalMissingOnBase { message: String },
+ #[error("{message}")]
+ InvalidFieldSharing { message: String },
+ #[error("{message}")]
+ InvalidShareableUsage { message: String },
+ #[error("{message}")]
+ InvalidLinkDirectiveUsage { message: String },
+ #[error("{message}")]
+ InvalidLinkIdentifier { message: String },
+ #[error("{message}")]
+ LinkImportNameMismatch { message: String },
+ #[error("{message}")]
+ ReferencedInaccessible { message: String },
+ #[error("{message}")]
+ DefaultValueUsesInaccessible { message: String },
+ #[error("{message}")]
+ QueryRootTypeInaccessible { message: String },
+ #[error("{message}")]
+ RequiredInaccessible { message: String },
+ #[error("{message}")]
+ ImplementedByInaccessible { message: String },
+ #[error("{message}")]
+ DisallowedInaccessible { message: String },
+ #[error("{message}")]
+ OnlyInaccessibleChildren { message: String },
+ #[error("{message}")]
+ RequiredInputFieldMissingInSomeSubgraph { message: String },
+ #[error("{message}")]
+ RequiredArgumentMissingInSomeSubgraph { message: String },
+ #[error("{message}")]
+ EmptyMergedInputType { message: String },
+ #[error("{message}")]
+ EnumValueMismatch { message: String },
+ #[error("{message}")]
+ EmptyMergedEnumType { message: String },
+ #[error("{message}")]
+ ShareableHasMismatchedRuntimeTypes { message: String },
+ #[error("{message}")]
+ SatisfiabilityError { message: String },
+ #[error("{message}")]
+ OverrideFromSelfError { message: String },
+ #[error("{message}")]
+ OverrideSourceHasOverride { message: String },
+ #[error("{message}")]
+ OverrideCollisionWithAnotherDirective { message: String },
+ #[error("{message}")]
+ OverrideOnInterface { message: String },
+ #[error("{message}")]
+ UnsupportedFeature { message: String },
+ #[error("{message}")]
+ InvalidFederationSupergraph { message: String },
+ #[error("{message}")]
+ DownstreamServiceError { message: String },
+ #[error("{message}")]
+ DirectiveCompositionError { message: String },
+ #[error("{message}")]
+ InterfaceObjectUsageError { message: String },
+ #[error("{message}")]
+ InterfaceKeyNotOnImplementation { message: String },
+ #[error("{message}")]
+ InterfaceKeyMissingImplementationType { message: String },
+ #[error("@defer is not supported on subscriptions")]
+ DeferredSubscriptionUnsupported,
+}
+
+impl SingleFederationError {
+ pub fn code(&self) -> ErrorCode {
+ match self {
+ SingleFederationError::Internal { .. } => ErrorCode::Internal,
+ SingleFederationError::InvalidGraphQL { .. } => ErrorCode::InvalidGraphQL,
+ SingleFederationError::DirectiveDefinitionInvalid { .. } => {
+ ErrorCode::DirectiveDefinitionInvalid
+ }
+ SingleFederationError::TypeDefinitionInvalid { .. } => ErrorCode::TypeDefinitionInvalid,
+ SingleFederationError::UnsupportedLinkedFeature { .. } => {
+ ErrorCode::UnsupportedLinkedFeature
+ }
+ SingleFederationError::UnknownFederationLinkVersion { .. } => {
+ ErrorCode::UnknownFederationLinkVersion
+ }
+ SingleFederationError::UnknownLinkVersion { .. } => ErrorCode::UnknownLinkVersion,
+ SingleFederationError::KeyFieldsHasArgs { .. } => ErrorCode::KeyFieldsHasArgs,
+ SingleFederationError::ProvidesFieldsHasArgs { .. } => ErrorCode::ProvidesFieldsHasArgs,
+ SingleFederationError::ProvidesFieldsMissingExternal { .. } => {
+ ErrorCode::ProvidesFieldsMissingExternal
+ }
+ SingleFederationError::RequiresFieldsMissingExternal { .. } => {
+ ErrorCode::RequiresFieldsMissingExternal
+ }
+ SingleFederationError::KeyUnsupportedOnInterface { .. } => {
+ ErrorCode::KeyUnsupportedOnInterface
+ }
+ SingleFederationError::ProvidesUnsupportedOnInterface { .. } => {
+ ErrorCode::ProvidesUnsupportedOnInterface
+ }
+ SingleFederationError::RequiresUnsupportedOnInterface { .. } => {
+ ErrorCode::RequiresUnsupportedOnInterface
+ }
+ SingleFederationError::KeyDirectiveInFieldsArgs { .. } => {
+ ErrorCode::KeyDirectiveInFieldsArgs
+ }
+ SingleFederationError::ProvidesDirectiveInFieldsArgs { .. } => {
+ ErrorCode::ProvidesDirectiveInFieldsArgs
+ }
+ SingleFederationError::RequiresDirectiveInFieldsArgs { .. } => {
+ ErrorCode::RequiresDirectiveInFieldsArgs
+ }
+ SingleFederationError::ExternalUnused { .. } => ErrorCode::ExternalUnused,
+ SingleFederationError::TypeWithOnlyUnusedExternal { .. } => {
+ ErrorCode::TypeWithOnlyUnusedExternal
+ }
+ SingleFederationError::ProvidesOnNonObjectField { .. } => {
+ ErrorCode::ProvidesOnNonObjectField
+ }
+ SingleFederationError::KeyInvalidFieldsType { .. } => ErrorCode::KeyInvalidFieldsType,
+ SingleFederationError::ProvidesInvalidFieldsType { .. } => {
+ ErrorCode::ProvidesInvalidFieldsType
+ }
+ SingleFederationError::RequiresInvalidFieldsType { .. } => {
+ ErrorCode::RequiresInvalidFieldsType
+ }
+ SingleFederationError::KeyInvalidFields { .. } => ErrorCode::KeyInvalidFields,
+ SingleFederationError::ProvidesInvalidFields { .. } => ErrorCode::ProvidesInvalidFields,
+ SingleFederationError::RequiresInvalidFields { .. } => ErrorCode::RequiresInvalidFields,
+ SingleFederationError::KeyFieldsSelectInvalidType { .. } => {
+ ErrorCode::KeyFieldsSelectInvalidType
+ }
+ SingleFederationError::RootQueryUsed { .. } => ErrorCode::RootQueryUsed,
+ SingleFederationError::RootMutationUsed { .. } => ErrorCode::RootMutationUsed,
+ SingleFederationError::RootSubscriptionUsed { .. } => ErrorCode::RootSubscriptionUsed,
+ SingleFederationError::InvalidSubgraphName { .. } => ErrorCode::InvalidSubgraphName,
+ SingleFederationError::NoQueries { .. } => ErrorCode::NoQueries,
+ SingleFederationError::InterfaceFieldNoImplem { .. } => {
+ ErrorCode::InterfaceFieldNoImplem
+ }
+ SingleFederationError::TypeKindMismatch { .. } => ErrorCode::TypeKindMismatch,
+ SingleFederationError::ExternalTypeMismatch { .. } => ErrorCode::ExternalTypeMismatch,
+ SingleFederationError::ExternalCollisionWithAnotherDirective { .. } => {
+ ErrorCode::ExternalCollisionWithAnotherDirective
+ }
+ SingleFederationError::ExternalArgumentMissing { .. } => {
+ ErrorCode::ExternalArgumentMissing
+ }
+ SingleFederationError::ExternalArgumentTypeMismatch { .. } => {
+ ErrorCode::ExternalArgumentTypeMismatch
+ }
+ SingleFederationError::ExternalArgumentDefaultMismatch { .. } => {
+ ErrorCode::ExternalArgumentDefaultMismatch
+ }
+ SingleFederationError::ExternalOnInterface { .. } => ErrorCode::ExternalOnInterface,
+ SingleFederationError::MergedDirectiveApplicationOnExternal { .. } => {
+ ErrorCode::MergedDirectiveApplicationOnExternal
+ }
+ SingleFederationError::FieldTypeMismatch { .. } => ErrorCode::FieldTypeMismatch,
+ SingleFederationError::FieldArgumentTypeMismatch { .. } => {
+ ErrorCode::FieldArgumentTypeMismatch
+ }
+ SingleFederationError::InputFieldDefaultMismatch { .. } => {
+ ErrorCode::InputFieldDefaultMismatch
+ }
+ SingleFederationError::FieldArgumentDefaultMismatch { .. } => {
+ ErrorCode::FieldArgumentDefaultMismatch
+ }
+ SingleFederationError::ExtensionWithNoBase { .. } => ErrorCode::ExtensionWithNoBase,
+ SingleFederationError::ExternalMissingOnBase { .. } => ErrorCode::ExternalMissingOnBase,
+ SingleFederationError::InvalidFieldSharing { .. } => ErrorCode::InvalidFieldSharing,
+ SingleFederationError::InvalidShareableUsage { .. } => ErrorCode::InvalidShareableUsage,
+ SingleFederationError::InvalidLinkDirectiveUsage { .. } => {
+ ErrorCode::InvalidLinkDirectiveUsage
+ }
+ SingleFederationError::InvalidLinkIdentifier { .. } => ErrorCode::InvalidLinkIdentifier,
+ SingleFederationError::LinkImportNameMismatch { .. } => {
+ ErrorCode::LinkImportNameMismatch
+ }
+ SingleFederationError::ReferencedInaccessible { .. } => {
+ ErrorCode::ReferencedInaccessible
+ }
+ SingleFederationError::DefaultValueUsesInaccessible { .. } => {
+ ErrorCode::DefaultValueUsesInaccessible
+ }
+ SingleFederationError::QueryRootTypeInaccessible { .. } => {
+ ErrorCode::QueryRootTypeInaccessible
+ }
+ SingleFederationError::RequiredInaccessible { .. } => ErrorCode::RequiredInaccessible,
+ SingleFederationError::ImplementedByInaccessible { .. } => {
+ ErrorCode::ImplementedByInaccessible
+ }
+ SingleFederationError::DisallowedInaccessible { .. } => {
+ ErrorCode::DisallowedInaccessible
+ }
+ SingleFederationError::OnlyInaccessibleChildren { .. } => {
+ ErrorCode::OnlyInaccessibleChildren
+ }
+ SingleFederationError::RequiredInputFieldMissingInSomeSubgraph { .. } => {
+ ErrorCode::RequiredInputFieldMissingInSomeSubgraph
+ }
+ SingleFederationError::RequiredArgumentMissingInSomeSubgraph { .. } => {
+ ErrorCode::RequiredArgumentMissingInSomeSubgraph
+ }
+ SingleFederationError::EmptyMergedInputType { .. } => ErrorCode::EmptyMergedInputType,
+ SingleFederationError::EnumValueMismatch { .. } => ErrorCode::EnumValueMismatch,
+ SingleFederationError::EmptyMergedEnumType { .. } => ErrorCode::EmptyMergedEnumType,
+ SingleFederationError::ShareableHasMismatchedRuntimeTypes { .. } => {
+ ErrorCode::ShareableHasMismatchedRuntimeTypes
+ }
+ SingleFederationError::SatisfiabilityError { .. } => ErrorCode::SatisfiabilityError,
+ SingleFederationError::OverrideFromSelfError { .. } => ErrorCode::OverrideFromSelfError,
+ SingleFederationError::OverrideSourceHasOverride { .. } => {
+ ErrorCode::OverrideSourceHasOverride
+ }
+ SingleFederationError::OverrideCollisionWithAnotherDirective { .. } => {
+ ErrorCode::OverrideCollisionWithAnotherDirective
+ }
+ SingleFederationError::OverrideOnInterface { .. } => ErrorCode::OverrideOnInterface,
+ SingleFederationError::UnsupportedFeature { .. } => ErrorCode::UnsupportedFeature,
+ SingleFederationError::InvalidFederationSupergraph { .. } => {
+ ErrorCode::InvalidFederationSupergraph
+ }
+ SingleFederationError::DownstreamServiceError { .. } => {
+ ErrorCode::DownstreamServiceError
+ }
+ SingleFederationError::DirectiveCompositionError { .. } => {
+ ErrorCode::DirectiveCompositionError
+ }
+ SingleFederationError::InterfaceObjectUsageError { .. } => {
+ ErrorCode::InterfaceObjectUsageError
+ }
+ SingleFederationError::InterfaceKeyNotOnImplementation { .. } => {
+ ErrorCode::InterfaceKeyNotOnImplementation
+ }
+ SingleFederationError::InterfaceKeyMissingImplementationType { .. } => {
+ ErrorCode::InterfaceKeyMissingImplementationType
+ }
+ SingleFederationError::DeferredSubscriptionUnsupported => ErrorCode::Internal,
+ }
+ }
+}
+
+impl From for SingleFederationError {
+ fn from(err: InvalidNameError) -> Self {
+ SingleFederationError::InvalidGraphQL {
+ message: format!("Invalid GraphQL name \"{}\"", err.0),
+ }
+ }
+}
+
+impl From for FederationError {
+ fn from(err: InvalidNameError) -> Self {
+ SingleFederationError::from(err).into()
+ }
+}
+
+impl From for FederationError {
+ fn from(_err: FederationSpecError) -> Self {
+ // TODO: When we get around to finishing the composition port, we should really switch it to
+ // using FederationError instead of FederationSpecError.
+ todo!()
+ }
+}
+
+#[derive(Debug, Clone, thiserror::Error)]
+pub struct MultipleFederationErrors {
+ pub errors: Vec,
+}
+
+impl MultipleFederationErrors {
+ pub fn push(&mut self, error: FederationError) {
+ match error {
+ FederationError::SingleFederationError(error) => {
+ self.errors.push(error);
+ }
+ FederationError::MultipleFederationErrors(errors) => {
+ self.errors.extend(errors.errors);
+ }
+ FederationError::AggregateFederationError(errors) => {
+ self.errors.extend(errors.causes);
+ }
+ }
+ }
+}
+
+impl Display for MultipleFederationErrors {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ write!(f, "The following errors occurred:")?;
+ for error in &self.errors {
+ write!(f, "\n\n - ")?;
+ for c in error.to_string().chars() {
+ if c == '\n' {
+ write!(f, "\n ")?;
+ } else {
+ f.write_char(c)?;
+ }
+ }
+ }
+ Ok(())
+ }
+}
+
+impl From for MultipleFederationErrors {
+ fn from(value: DiagnosticList) -> Self {
+ Self {
+ errors: value
+ .iter()
+ .map(|e| SingleFederationError::InvalidGraphQL {
+ message: e.error.to_string(),
+ })
+ .collect(),
+ }
+ }
+}
+
+impl FromIterator for MultipleFederationErrors {
+ fn from_iter>(iter: T) -> Self {
+ Self {
+ errors: iter.into_iter().collect(),
+ }
+ }
+}
+
+#[derive(Debug, Clone, thiserror::Error)]
+pub struct AggregateFederationError {
+ pub code: String,
+ pub message: String,
+ pub causes: Vec,
+}
+
+impl Display for AggregateFederationError {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ write!(f, "[{}] {}\nCaused by:", self.code, self.message)?;
+ for error in &self.causes {
+ write!(f, "\n\n - ")?;
+ for c in error.to_string().chars() {
+ if c == '\n' {
+ write!(f, "\n ")?;
+ } else {
+ f.write_char(c)?;
+ }
+ }
+ }
+ Ok(())
+ }
+}
+
+// PORT_NOTE: Often times, JS functions would either throw/return a GraphQLError, return a vector
+// of GraphQLErrors, or take a vector of GraphQLErrors and group them together under an
+// AggregateGraphQLError which itself would have a specific error message and code, and throw that.
+// We represent all these cases with an enum, and delegate to the members.
+#[derive(Debug, Clone, thiserror::Error)]
+pub enum FederationError {
+ #[error(transparent)]
+ SingleFederationError(#[from] SingleFederationError),
+ #[error(transparent)]
+ MultipleFederationErrors(#[from] MultipleFederationErrors),
+ #[error(transparent)]
+ AggregateFederationError(#[from] AggregateFederationError),
+}
+
+impl From for FederationError {
+ fn from(value: DiagnosticList) -> Self {
+ let value: MultipleFederationErrors = value.into();
+ value.into()
+ }
+}
+
+impl From> for FederationError {
+ fn from(value: WithErrors) -> Self {
+ value.errors.into()
+ }
+}
+
+impl FederationError {
+ pub(crate) fn internal(message: impl Into) -> Self {
+ SingleFederationError::Internal {
+ message: message.into(),
+ }
+ .into()
+ }
+}
+
+impl MultipleFederationErrors {
+ /// Converts into `Result<(), FederationError>`.
+ /// - The return value can be either Ok, Err with a SingleFederationError or MultipleFederationErrors,
+ /// depending on the number of errors in the input.
+ pub fn into_result(self) -> Result<(), FederationError> {
+ match self.errors.len().cmp(&1) {
+ Ordering::Less => Ok(()),
+ Ordering::Equal => Err(self.errors[0].clone().into()),
+ Ordering::Greater => Err(self.into()),
+ }
+ }
+}
+
+// We didn't track errors addition precisely pre-2.0 and tracking it now has an unclear ROI, so we
+// just mark all the error code that predates 2.0 as 0.x.
+const FED1_CODE: &str = "0.x";
+
+#[derive(Debug, Clone)]
+pub struct ErrorCodeMetadata {
+ pub added_in: &'static str,
+ pub replaces: &'static [&'static str],
+}
+
+#[derive(Debug)]
+pub struct ErrorCodeDefinition {
+ code: String,
+ // PORT_NOTE: Known as "description" in the JS code. The name was changed to distinguish it from
+ // Error.description().
+ doc_description: String,
+ metadata: ErrorCodeMetadata,
+}
+
+impl ErrorCodeDefinition {
+ fn new(code: String, doc_description: String, metadata: Option) -> Self {
+ Self {
+ code,
+ doc_description,
+ metadata: metadata.unwrap_or_else(|| DEFAULT_METADATA.clone()),
+ }
+ }
+
+ pub fn code(&self) -> &str {
+ &self.code
+ }
+
+ pub fn doc_description(&self) -> &str {
+ &self.doc_description
+ }
+
+ pub fn metadata(&self) -> &ErrorCodeMetadata {
+ &self.metadata
+ }
+}
+
+/*
+ * Most codes currently originate from the initial fed 2 release so we use this for convenience.
+ * This can be changed later, inline versions everywhere, if that becomes irrelevant.
+ */
+static DEFAULT_METADATA: ErrorCodeMetadata = ErrorCodeMetadata {
+ added_in: "2.0.0",
+ replaces: &[],
+};
+
+struct ErrorCodeCategory> {
+ // Fn(element: TElement) -> String
+ extract_code: Box String>,
+ // Fn(element: TElement) -> String
+ make_doc_description: Box String>,
+ metadata: ErrorCodeMetadata,
+}
+
+impl> ErrorCodeCategory {
+ fn new(
+ extract_code: Box String>,
+ make_doc_description: Box String>,
+ metadata: Option,
+ ) -> Self {
+ Self {
+ extract_code,
+ make_doc_description,
+ metadata: metadata.unwrap_or_else(|| DEFAULT_METADATA.clone()),
+ }
+ }
+
+ // PORT_NOTE: The Typescript type in the JS code only has get(), but I also added createCode()
+ // here since it's used in the return type of makeErrorCodeCategory().
+ fn create_code(&self, element: TElement) -> ErrorCodeDefinition {
+ ErrorCodeDefinition::new(
+ (self.extract_code)(element.clone()),
+ (self.make_doc_description)(element),
+ Some(self.metadata.clone()),
+ )
+ }
+}
+
+impl ErrorCodeCategory {
+ fn new_federation_directive(
+ code_suffix: String,
+ make_doc_description: Box String>,
+ metadata: Option,
+ ) -> Self {
+ Self::new(
+ Box::new(move |element: String| format!("{}_{}", element.to_uppercase(), code_suffix)),
+ make_doc_description,
+ metadata,
+ )
+ }
+}
+
+lazy_static! {
+ static ref INVALID_GRAPHQL: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "INVALID_GRAPHQL".to_owned(),
+ "A schema is invalid GraphQL: it violates one of the rule of the specification.".to_owned(),
+ None,
+ );
+
+ static ref DIRECTIVE_DEFINITION_INVALID: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "DIRECTIVE_DEFINITION_INVALID".to_owned(),
+ "A built-in or federation directive has an invalid definition in the schema.".to_owned(),
+ Some(ErrorCodeMetadata {
+ replaces: &["TAG_DEFINITION_INVALID"],
+ ..DEFAULT_METADATA.clone()
+ }),
+ );
+
+ static ref TYPE_DEFINITION_INVALID: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "TYPE_DEFINITION_INVALID".to_owned(),
+ "A built-in or federation type has an invalid definition in the schema.".to_owned(),
+ None,
+ );
+
+ static ref UNSUPPORTED_LINKED_FEATURE: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "UNSUPPORTED_LINKED_FEATURE".to_owned(),
+ "Indicates that a feature used in a @link is either unsupported or is used with unsupported options.".to_owned(),
+ None,
+ );
+
+ static ref UNKNOWN_FEDERATION_LINK_VERSION: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "UNKNOWN_FEDERATION_LINK_VERSION".to_owned(),
+ "The version of federation in a @link directive on the schema is unknown.".to_owned(),
+ None,
+ );
+
+ static ref UNKNOWN_LINK_VERSION: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "UNKNOWN_LINK_VERSION".to_owned(),
+ "The version of @link set on the schema is unknown.".to_owned(),
+ Some(ErrorCodeMetadata {
+ added_in: "2.1.0",
+ replaces: &[],
+ }),
+ );
+
+ static ref FIELDS_HAS_ARGS: ErrorCodeCategory = ErrorCodeCategory::new_federation_directive(
+ "FIELDS_HAS_ARGS".to_owned(),
+ Box::new(|directive| format!("The `fields` argument of a `@{}` directive includes a field defined with arguments (which is not currently supported).", directive)),
+ None,
+ );
+
+ static ref KEY_FIELDS_HAS_ARGS: ErrorCodeDefinition = FIELDS_HAS_ARGS.create_code("key".to_owned());
+ static ref PROVIDES_FIELDS_HAS_ARGS: ErrorCodeDefinition = FIELDS_HAS_ARGS.create_code("provides".to_owned());
+
+ static ref DIRECTIVE_FIELDS_MISSING_EXTERNAL: ErrorCodeCategory = ErrorCodeCategory::new_federation_directive(
+ "FIELDS_MISSING_EXTERNAL".to_owned(),
+ Box::new(|directive| format!("The `fields` argument of a `@{}` directive includes a field that is not marked as `@external`.", directive)),
+ Some(ErrorCodeMetadata {
+ added_in: FED1_CODE,
+ replaces: &[],
+ }),
+ );
+
+ static ref PROVIDES_FIELDS_MISSING_EXTERNAL: ErrorCodeDefinition =
+ DIRECTIVE_FIELDS_MISSING_EXTERNAL.create_code("provides".to_owned());
+ static ref REQUIRES_FIELDS_MISSING_EXTERNAL: ErrorCodeDefinition =
+ DIRECTIVE_FIELDS_MISSING_EXTERNAL.create_code("requires".to_owned());
+
+ static ref DIRECTIVE_UNSUPPORTED_ON_INTERFACE: ErrorCodeCategory = ErrorCodeCategory::new_federation_directive(
+ "UNSUPPORTED_ON_INTERFACE".to_owned(),
+ Box::new(|directive| {
+ let suffix = if directive == "key" {
+ "only supported when @linking to federation 2.3+"
+ } else {
+ "not (yet) supported"
+ };
+ format!(
+ "A `@{}` directive is used on an interface, which is {}.",
+ directive, suffix
+ )
+ }),
+ None,
+ );
+
+ static ref KEY_UNSUPPORTED_ON_INTERFACE: ErrorCodeDefinition =
+ DIRECTIVE_UNSUPPORTED_ON_INTERFACE.create_code("key".to_owned());
+ static ref PROVIDES_UNSUPPORTED_ON_INTERFACE: ErrorCodeDefinition =
+ DIRECTIVE_UNSUPPORTED_ON_INTERFACE.create_code("provides".to_owned());
+ static ref REQUIRES_UNSUPPORTED_ON_INTERFACE: ErrorCodeDefinition =
+ DIRECTIVE_UNSUPPORTED_ON_INTERFACE.create_code("requires".to_owned());
+
+ static ref DIRECTIVE_IN_FIELDS_ARG: ErrorCodeCategory = ErrorCodeCategory::new_federation_directive(
+ "DIRECTIVE_IN_FIELDS_ARG".to_owned(),
+ Box::new(|directive| format!("The `fields` argument of a `@{}` directive includes some directive applications. This is not supported", directive)),
+ Some(ErrorCodeMetadata {
+ added_in: "2.1.0",
+ replaces: &[],
+ }),
+ );
+
+ static ref KEY_DIRECTIVE_IN_FIELDS_ARGS: ErrorCodeDefinition = DIRECTIVE_IN_FIELDS_ARG.create_code("key".to_owned());
+ static ref PROVIDES_DIRECTIVE_IN_FIELDS_ARGS: ErrorCodeDefinition = DIRECTIVE_IN_FIELDS_ARG.create_code("provides".to_owned());
+ static ref REQUIRES_DIRECTIVE_IN_FIELDS_ARGS: ErrorCodeDefinition = DIRECTIVE_IN_FIELDS_ARG.create_code("requires".to_owned());
+
+ static ref EXTERNAL_UNUSED: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "EXTERNAL_UNUSED".to_owned(),
+ "An `@external` field is not being used by any instance of `@key`, `@requires`, `@provides` or to satisfy an interface implementation.".to_owned(),
+ Some(ErrorCodeMetadata {
+ added_in: FED1_CODE,
+ replaces: &[],
+ }),
+ );
+
+ static ref TYPE_WITH_ONLY_UNUSED_EXTERNAL: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "TYPE_WITH_ONLY_UNUSED_EXTERNAL".to_owned(),
+ [
+ "A federation 1 schema has a composite type comprised only of unused external fields.".to_owned(),
+ format!("Note that this error can _only_ be raised for federation 1 schema as federation 2 schema do not allow unused external fields (and errors with code {} will be raised in that case).", EXTERNAL_UNUSED.code),
+ "But when federation 1 schema are automatically migrated to federation 2 ones, unused external fields are automatically removed, and in rare case this can leave a type empty. If that happens, an error with this code will be raised".to_owned()
+ ].join(" "),
+ None,
+ );
+
+ static ref PROVIDES_ON_NON_OBJECT_FIELD: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "PROVIDES_ON_NON_OBJECT_FIELD".to_owned(),
+ "A `@provides` directive is used to mark a field whose base type is not an object type.".to_owned(),
+ None,
+ );
+
+ static ref DIRECTIVE_INVALID_FIELDS_TYPE: ErrorCodeCategory = ErrorCodeCategory::new_federation_directive(
+ "INVALID_FIELDS_TYPE".to_owned(),
+ Box::new(|directive| format!("The value passed to the `fields` argument of a `@{}` directive is not a string.", directive)),
+ None,
+ );
+
+ static ref KEY_INVALID_FIELDS_TYPE: ErrorCodeDefinition =
+ DIRECTIVE_INVALID_FIELDS_TYPE.create_code("key".to_owned());
+ static ref PROVIDES_INVALID_FIELDS_TYPE: ErrorCodeDefinition =
+ DIRECTIVE_INVALID_FIELDS_TYPE.create_code("provides".to_owned());
+ static ref REQUIRES_INVALID_FIELDS_TYPE: ErrorCodeDefinition =
+ DIRECTIVE_INVALID_FIELDS_TYPE.create_code("requires".to_owned());
+
+ static ref DIRECTIVE_INVALID_FIELDS: ErrorCodeCategory = ErrorCodeCategory::new_federation_directive(
+ "INVALID_FIELDS".to_owned(),
+ Box::new(|directive| format!("The `fields` argument of a `@{}` directive is invalid (it has invalid syntax, includes unknown fields, ...).", directive)),
+ None,
+ );
+
+ static ref KEY_INVALID_FIELDS: ErrorCodeDefinition =
+ DIRECTIVE_INVALID_FIELDS.create_code("key".to_owned());
+ static ref PROVIDES_INVALID_FIELDS: ErrorCodeDefinition =
+ DIRECTIVE_INVALID_FIELDS.create_code("provides".to_owned());
+ static ref REQUIRES_INVALID_FIELDS: ErrorCodeDefinition =
+ DIRECTIVE_INVALID_FIELDS.create_code("requires".to_owned());
+
+ static ref KEY_FIELDS_SELECT_INVALID_TYPE: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "KEY_FIELDS_SELECT_INVALID_TYPE".to_owned(),
+ "The `fields` argument of `@key` directive includes a field whose type is a list, interface, or union type. Fields of these types cannot be part of a `@key`".to_owned(),
+ Some(ErrorCodeMetadata {
+ added_in: FED1_CODE,
+ replaces: &[],
+ }),
+ );
+
+ static ref ROOT_TYPE_USED: ErrorCodeCategory = ErrorCodeCategory::new(
+ Box::new(|element| {
+ let kind: String = element.into();
+ format!("ROOT_{}_USED", kind.to_uppercase())
+ }),
+ Box::new(|element| {
+ let kind: String = element.into();
+ format!("A subgraph's schema defines a type with the name `{}`, while also specifying a _different_ type name as the root query object. This is not allowed.", kind)
+ }),
+ Some(ErrorCodeMetadata {
+ added_in: FED1_CODE,
+ replaces: &[],
+ })
+
+ );
+
+ static ref ROOT_QUERY_USED: ErrorCodeDefinition = ROOT_TYPE_USED.create_code(SchemaRootKind::Query);
+ static ref ROOT_MUTATION_USED: ErrorCodeDefinition = ROOT_TYPE_USED.create_code(SchemaRootKind::Mutation);
+ static ref ROOT_SUBSCRIPTION_USED: ErrorCodeDefinition = ROOT_TYPE_USED.create_code(SchemaRootKind::Subscription);
+
+ static ref INVALID_SUBGRAPH_NAME: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "INVALID_SUBGRAPH_NAME".to_owned(),
+ "A subgraph name is invalid (subgraph names cannot be a single underscore (\"_\")).".to_owned(),
+ None,
+ );
+
+ static ref NO_QUERIES: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "NO_QUERIES".to_owned(),
+ "None of the composed subgraphs expose any query.".to_owned(),
+ None,
+ );
+
+ static ref INTERFACE_FIELD_NO_IMPLEM: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "INTERFACE_FIELD_NO_IMPLEM".to_owned(),
+ "After subgraph merging, an implementation is missing a field of one of the interface it implements (which can happen for valid subgraphs).".to_owned(),
+ None,
+ );
+
+ static ref TYPE_KIND_MISMATCH: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "TYPE_KIND_MISMATCH".to_owned(),
+ "A type has the same name in different subgraphs, but a different kind. For instance, one definition is an object type but another is an interface.".to_owned(),
+ Some(ErrorCodeMetadata {
+ replaces: &["VALUE_TYPE_KIND_MISMATCH", "EXTENSION_OF_WRONG_KIND", "ENUM_MISMATCH_TYPE"],
+ ..DEFAULT_METADATA.clone()
+ }),
+ );
+
+ static ref EXTERNAL_TYPE_MISMATCH: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "EXTERNAL_TYPE_MISMATCH".to_owned(),
+ "An `@external` field has a type that is incompatible with the declaration(s) of that field in other subgraphs.".to_owned(),
+ Some(ErrorCodeMetadata {
+ added_in: FED1_CODE,
+ replaces: &[],
+ }),
+ );
+
+ static ref EXTERNAL_COLLISION_WITH_ANOTHER_DIRECTIVE: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "EXTERNAL_COLLISION_WITH_ANOTHER_DIRECTIVE".to_owned(),
+ "The @external directive collides with other directives in some situations.".to_owned(),
+ Some(ErrorCodeMetadata {
+ added_in: "2.1.0",
+ replaces: &[],
+ }),
+ );
+
+ static ref EXTERNAL_ARGUMENT_MISSING: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "EXTERNAL_ARGUMENT_MISSING".to_owned(),
+ "An `@external` field is missing some arguments present in the declaration(s) of that field in other subgraphs.".to_owned(),
+ None,
+ );
+
+ static ref EXTERNAL_ARGUMENT_TYPE_MISMATCH: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "EXTERNAL_ARGUMENT_TYPE_MISMATCH".to_owned(),
+ "An `@external` field declares an argument with a type that is incompatible with the corresponding argument in the declaration(s) of that field in other subgraphs.".to_owned(),
+ None,
+ );
+
+
+ static ref EXTERNAL_ARGUMENT_DEFAULT_MISMATCH: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "EXTERNAL_ARGUMENT_DEFAULT_MISMATCH".to_owned(),
+ "An `@external` field declares an argument with a default that is incompatible with the corresponding argument in the declaration(s) of that field in other subgraphs.".to_owned(),
+ None,
+ );
+
+ static ref EXTERNAL_ON_INTERFACE: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "EXTERNAL_ON_INTERFACE".to_owned(),
+ "The field of an interface type is marked with `@external`: as external is about marking field not resolved by the subgraph and as interface field are not resolved (only implementations of those fields are), an \"external\" interface field is nonsensical".to_owned(),
+ None,
+ );
+
+ static ref MERGED_DIRECTIVE_APPLICATION_ON_EXTERNAL: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "MERGED_DIRECTIVE_APPLICATION_ON_EXTERNAL".to_owned(),
+ "In a subgraph, a field is both marked @external and has a merged directive applied to it".to_owned(),
+ None,
+ );
+
+ static ref FIELD_TYPE_MISMATCH: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "FIELD_TYPE_MISMATCH".to_owned(),
+ "A field has a type that is incompatible with other declarations of that field in other subgraphs.".to_owned(),
+ Some(ErrorCodeMetadata {
+ replaces: &["VALUE_TYPE_FIELD_TYPE_MISMATCH"],
+ ..DEFAULT_METADATA.clone()
+ }),
+ );
+
+ static ref FIELD_ARGUMENT_TYPE_MISMATCH: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "FIELD_ARGUMENT_TYPE_MISMATCH".to_owned(),
+ "An argument (of a field/directive) has a type that is incompatible with that of other declarations of that same argument in other subgraphs.".to_owned(),
+ Some(ErrorCodeMetadata {
+ replaces: &["VALUE_TYPE_INPUT_VALUE_MISMATCH"],
+ ..DEFAULT_METADATA.clone()
+ }),
+ );
+
+ static ref INPUT_FIELD_DEFAULT_MISMATCH: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "INPUT_FIELD_DEFAULT_MISMATCH".to_owned(),
+ "An input field has a default value that is incompatible with other declarations of that field in other subgraphs.".to_owned(),
+ None,
+ );
+
+ static ref FIELD_ARGUMENT_DEFAULT_MISMATCH: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "FIELD_ARGUMENT_DEFAULT_MISMATCH".to_owned(),
+ "An argument (of a field/directive) has a default value that is incompatible with that of other declarations of that same argument in other subgraphs.".to_owned(),
+ None,
+ );
+
+ static ref EXTENSION_WITH_NO_BASE: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "EXTENSION_WITH_NO_BASE".to_owned(),
+ "A subgraph is attempting to `extend` a type that is not originally defined in any known subgraph.".to_owned(),
+ Some(ErrorCodeMetadata {
+ added_in: FED1_CODE,
+ replaces: &[],
+ }),
+ );
+
+ static ref EXTERNAL_MISSING_ON_BASE: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "EXTERNAL_MISSING_ON_BASE".to_owned(),
+ "A field is marked as `@external` in a subgraph but with no non-external declaration in any other subgraph.".to_owned(),
+ Some(ErrorCodeMetadata {
+ added_in: FED1_CODE,
+ replaces: &[],
+ }),
+ );
+
+ static ref INVALID_FIELD_SHARING: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "INVALID_FIELD_SHARING".to_owned(),
+ "A field that is non-shareable in at least one subgraph is resolved by multiple subgraphs.".to_owned(),
+ None,
+ );
+
+ static ref INVALID_SHAREABLE_USAGE: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "INVALID_SHAREABLE_USAGE".to_owned(),
+ "The `@shareable` federation directive is used in an invalid way.".to_owned(),
+ Some(ErrorCodeMetadata {
+ added_in: "2.1.2",
+ replaces: &[],
+ }),
+ );
+
+ static ref INVALID_LINK_DIRECTIVE_USAGE: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "INVALID_LINK_DIRECTIVE_USAGE".to_owned(),
+ "An application of the @link directive is invalid/does not respect the specification.".to_owned(),
+ None,
+ );
+
+ static ref INVALID_LINK_IDENTIFIER: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "INVALID_LINK_IDENTIFIER".to_owned(),
+ "A url/version for a @link feature is invalid/does not respect the specification.".to_owned(),
+ Some(ErrorCodeMetadata {
+ added_in: "2.1.0",
+ replaces: &[],
+ }),
+ );
+
+ static ref LINK_IMPORT_NAME_MISMATCH: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "LINK_IMPORT_NAME_MISMATCH".to_owned(),
+ "The import name for a merged directive (as declared by the relevant `@link(import:)` argument) is inconsistent between subgraphs.".to_owned(),
+ None,
+ );
+
+ static ref REFERENCED_INACCESSIBLE: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "REFERENCED_INACCESSIBLE".to_owned(),
+ "An element is marked as @inaccessible but is referenced by an element visible in the API schema.".to_owned(),
+ None,
+ );
+
+ static ref DEFAULT_VALUE_USES_INACCESSIBLE: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "DEFAULT_VALUE_USES_INACCESSIBLE".to_owned(),
+ "An element is marked as @inaccessible but is used in the default value of an element visible in the API schema.".to_owned(),
+ None,
+ );
+
+ static ref QUERY_ROOT_TYPE_INACCESSIBLE: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "QUERY_ROOT_TYPE_INACCESSIBLE".to_owned(),
+ "An element is marked as @inaccessible but is the query root type, which must be visible in the API schema.".to_owned(),
+ None,
+ );
+
+ static ref REQUIRED_INACCESSIBLE: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "REQUIRED_INACCESSIBLE".to_owned(),
+ "An element is marked as @inaccessible but is required by an element visible in the API schema.".to_owned(),
+ None,
+ );
+
+ static ref IMPLEMENTED_BY_INACCESSIBLE: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "IMPLEMENTED_BY_INACCESSIBLE".to_owned(),
+ "An element is marked as @inaccessible but implements an element visible in the API schema.".to_owned(),
+ None,
+ );
+}
+
+// The above lazy_static! block hits recursion limit if we try to add more to it, so we start a
+// new block here.
+lazy_static! {
+ static ref DISALLOWED_INACCESSIBLE: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "DISALLOWED_INACCESSIBLE".to_owned(),
+ "An element is marked as @inaccessible that is not allowed to be @inaccessible.".to_owned(),
+ None,
+ );
+
+ static ref ONLY_INACCESSIBLE_CHILDREN: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "ONLY_INACCESSIBLE_CHILDREN".to_owned(),
+ "A type visible in the API schema has only @inaccessible children.".to_owned(),
+ None,
+ );
+
+ static ref REQUIRED_INPUT_FIELD_MISSING_IN_SOME_SUBGRAPH: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "REQUIRED_INPUT_FIELD_MISSING_IN_SOME_SUBGRAPH".to_owned(),
+ "A field of an input object type is mandatory in some subgraphs, but the field is not defined in all the subgraphs that define the input object type.".to_owned(),
+ None,
+ );
+
+ static ref REQUIRED_ARGUMENT_MISSING_IN_SOME_SUBGRAPH: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "REQUIRED_ARGUMENT_MISSING_IN_SOME_SUBGRAPH".to_owned(),
+ "An argument of a field or directive definition is mandatory in some subgraphs, but the argument is not defined in all the subgraphs that define the field or directive definition.".to_owned(),
+ None,
+ );
+
+ static ref EMPTY_MERGED_INPUT_TYPE: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "EMPTY_MERGED_INPUT_TYPE".to_owned(),
+ "An input object type has no field common to all the subgraphs that define the type. Merging that type would result in an invalid empty input object type.".to_owned(),
+ None,
+ );
+
+ static ref ENUM_VALUE_MISMATCH: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "ENUM_VALUE_MISMATCH".to_owned(),
+ "An enum type that is used as both an input and output type has a value that is not defined in all the subgraphs that define the enum type.".to_owned(),
+ None,
+ );
+
+ static ref EMPTY_MERGED_ENUM_TYPE: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "EMPTY_MERGED_ENUM_TYPE".to_owned(),
+ "An enum type has no value common to all the subgraphs that define the type. Merging that type would result in an invalid empty enum type.".to_owned(),
+ None,
+ );
+
+ static ref SHAREABLE_HAS_MISMATCHED_RUNTIME_TYPES: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "SHAREABLE_HAS_MISMATCHED_RUNTIME_TYPES".to_owned(),
+ "A shareable field return type has mismatched possible runtime types in the subgraphs in which the field is declared. As shared fields must resolve the same way in all subgraphs, this is almost surely a mistake.".to_owned(),
+ None,
+ );
+
+ static ref SATISFIABILITY_ERROR: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "SATISFIABILITY_ERROR".to_owned(),
+ "Subgraphs can be merged, but the resulting supergraph API would have queries that cannot be satisfied by those subgraphs.".to_owned(),
+ None,
+ );
+
+ static ref OVERRIDE_FROM_SELF_ERROR: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "OVERRIDE_FROM_SELF_ERROR".to_owned(),
+ "Field with `@override` directive has \"from\" location that references its own subgraph.".to_owned(),
+ None,
+ );
+
+ static ref OVERRIDE_SOURCE_HAS_OVERRIDE: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "OVERRIDE_SOURCE_HAS_OVERRIDE".to_owned(),
+ "Field which is overridden to another subgraph is also marked @override.".to_owned(),
+ None,
+ );
+
+ static ref OVERRIDE_COLLISION_WITH_ANOTHER_DIRECTIVE: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "OVERRIDE_COLLISION_WITH_ANOTHER_DIRECTIVE".to_owned(),
+ "The @override directive cannot be used on external fields, nor to override fields with either @external, @provides, or @requires.".to_owned(),
+ None,
+ );
+
+ static ref OVERRIDE_ON_INTERFACE: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "OVERRIDE_ON_INTERFACE".to_owned(),
+ "The @override directive cannot be used on the fields of an interface type.".to_owned(),
+ Some(ErrorCodeMetadata {
+ added_in: "2.3.0",
+ replaces: &[],
+ }),
+ );
+
+ static ref UNSUPPORTED_FEATURE: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "UNSUPPORTED_FEATURE".to_owned(),
+ "Indicates an error due to feature currently unsupported by federation.".to_owned(),
+ Some(ErrorCodeMetadata {
+ added_in: "2.1.0",
+ replaces: &[],
+ }),
+ );
+
+ static ref INVALID_FEDERATION_SUPERGRAPH: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "INVALID_FEDERATION_SUPERGRAPH".to_owned(),
+ "Indicates that a schema provided for an Apollo Federation supergraph is not a valid supergraph schema.".to_owned(),
+ Some(ErrorCodeMetadata {
+ added_in: "2.1.0",
+ replaces: &[],
+ }),
+ );
+
+ static ref DOWNSTREAM_SERVICE_ERROR: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "DOWNSTREAM_SERVICE_ERROR".to_owned(),
+ "Indicates an error in a subgraph service query during query execution in a federated service.".to_owned(),
+ Some(ErrorCodeMetadata {
+ added_in: FED1_CODE,
+ replaces: &[],
+ }),
+ );
+
+ static ref DIRECTIVE_COMPOSITION_ERROR: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "DIRECTIVE_COMPOSITION_ERROR".to_owned(),
+ "Error when composing custom directives.".to_owned(),
+ Some(ErrorCodeMetadata {
+ added_in: "2.1.0",
+ replaces: &[],
+ }),
+ );
+
+ static ref INTERFACE_OBJECT_USAGE_ERROR: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "INTERFACE_OBJECT_USAGE_ERROR".to_owned(),
+ "Error in the usage of the @interfaceObject directive.".to_owned(),
+ Some(ErrorCodeMetadata {
+ added_in: "2.3.0",
+ replaces: &[],
+ }),
+ );
+
+ static ref INTERFACE_KEY_NOT_ON_IMPLEMENTATION: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "INTERFACE_KEY_NOT_ON_IMPLEMENTATION".to_owned(),
+ "A `@key` is defined on an interface type, but is not defined (or is not resolvable) on at least one of the interface implementations".to_owned(),
+ Some(ErrorCodeMetadata {
+ added_in: "2.3.0",
+ replaces: &[],
+ }),
+ );
+
+ static ref INTERFACE_KEY_MISSING_IMPLEMENTATION_TYPE: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "INTERFACE_KEY_MISSING_IMPLEMENTATION_TYPE".to_owned(),
+ "A subgraph has a `@key` on an interface type, but that subgraph does not define an implementation (in the supergraph) of that interface".to_owned(),
+ Some(ErrorCodeMetadata {
+ added_in: "2.3.0",
+ replaces: &[],
+ }),
+ );
+
+ static ref INTERNAL: ErrorCodeDefinition = ErrorCodeDefinition::new(
+ "INTERNAL".to_owned(),
+ "An internal federation error occured.".to_owned(),
+ None,
+ );
+}
+
+#[derive(Debug, strum_macros::EnumIter)]
+pub enum ErrorCode {
+ Internal,
+ InvalidGraphQL,
+ DirectiveDefinitionInvalid,
+ TypeDefinitionInvalid,
+ UnsupportedLinkedFeature,
+ UnknownFederationLinkVersion,
+ UnknownLinkVersion,
+ KeyFieldsHasArgs,
+ ProvidesFieldsHasArgs,
+ ProvidesFieldsMissingExternal,
+ RequiresFieldsMissingExternal,
+ KeyUnsupportedOnInterface,
+ ProvidesUnsupportedOnInterface,
+ RequiresUnsupportedOnInterface,
+ KeyDirectiveInFieldsArgs,
+ ProvidesDirectiveInFieldsArgs,
+ RequiresDirectiveInFieldsArgs,
+ ExternalUnused,
+ TypeWithOnlyUnusedExternal,
+ ProvidesOnNonObjectField,
+ KeyInvalidFieldsType,
+ ProvidesInvalidFieldsType,
+ RequiresInvalidFieldsType,
+ KeyInvalidFields,
+ ProvidesInvalidFields,
+ RequiresInvalidFields,
+ KeyFieldsSelectInvalidType,
+ RootQueryUsed,
+ RootMutationUsed,
+ RootSubscriptionUsed,
+ InvalidSubgraphName,
+ NoQueries,
+ InterfaceFieldNoImplem,
+ TypeKindMismatch,
+ ExternalTypeMismatch,
+ ExternalCollisionWithAnotherDirective,
+ ExternalArgumentMissing,
+ ExternalArgumentTypeMismatch,
+ ExternalArgumentDefaultMismatch,
+ ExternalOnInterface,
+ MergedDirectiveApplicationOnExternal,
+ FieldTypeMismatch,
+ FieldArgumentTypeMismatch,
+ InputFieldDefaultMismatch,
+ FieldArgumentDefaultMismatch,
+ ExtensionWithNoBase,
+ ExternalMissingOnBase,
+ InvalidFieldSharing,
+ InvalidShareableUsage,
+ InvalidLinkDirectiveUsage,
+ InvalidLinkIdentifier,
+ LinkImportNameMismatch,
+ ReferencedInaccessible,
+ DefaultValueUsesInaccessible,
+ QueryRootTypeInaccessible,
+ RequiredInaccessible,
+ ImplementedByInaccessible,
+ DisallowedInaccessible,
+ OnlyInaccessibleChildren,
+ RequiredInputFieldMissingInSomeSubgraph,
+ RequiredArgumentMissingInSomeSubgraph,
+ EmptyMergedInputType,
+ EnumValueMismatch,
+ EmptyMergedEnumType,
+ ShareableHasMismatchedRuntimeTypes,
+ SatisfiabilityError,
+ OverrideFromSelfError,
+ OverrideSourceHasOverride,
+ OverrideCollisionWithAnotherDirective,
+ OverrideOnInterface,
+ UnsupportedFeature,
+ InvalidFederationSupergraph,
+ DownstreamServiceError,
+ DirectiveCompositionError,
+ InterfaceObjectUsageError,
+ InterfaceKeyNotOnImplementation,
+ InterfaceKeyMissingImplementationType,
+}
+
+impl ErrorCode {
+ pub fn definition(&self) -> &'static ErrorCodeDefinition {
+ match self {
+ // TODO: We should determine the code and doc info for internal errors.
+ ErrorCode::Internal => &INTERNAL,
+ ErrorCode::InvalidGraphQL => &INVALID_GRAPHQL,
+ ErrorCode::DirectiveDefinitionInvalid => &DIRECTIVE_DEFINITION_INVALID,
+ ErrorCode::TypeDefinitionInvalid => &TYPE_DEFINITION_INVALID,
+ ErrorCode::UnsupportedLinkedFeature => &UNSUPPORTED_LINKED_FEATURE,
+ ErrorCode::UnknownFederationLinkVersion => &UNKNOWN_FEDERATION_LINK_VERSION,
+ ErrorCode::UnknownLinkVersion => &UNKNOWN_LINK_VERSION,
+ ErrorCode::KeyFieldsHasArgs => &KEY_FIELDS_HAS_ARGS,
+ ErrorCode::ProvidesFieldsHasArgs => &PROVIDES_FIELDS_HAS_ARGS,
+ ErrorCode::ProvidesFieldsMissingExternal => &PROVIDES_FIELDS_MISSING_EXTERNAL,
+ ErrorCode::RequiresFieldsMissingExternal => &REQUIRES_FIELDS_MISSING_EXTERNAL,
+ ErrorCode::KeyUnsupportedOnInterface => &KEY_UNSUPPORTED_ON_INTERFACE,
+ ErrorCode::ProvidesUnsupportedOnInterface => &PROVIDES_UNSUPPORTED_ON_INTERFACE,
+ ErrorCode::RequiresUnsupportedOnInterface => &REQUIRES_UNSUPPORTED_ON_INTERFACE,
+ ErrorCode::KeyDirectiveInFieldsArgs => &KEY_DIRECTIVE_IN_FIELDS_ARGS,
+ ErrorCode::ProvidesDirectiveInFieldsArgs => &PROVIDES_DIRECTIVE_IN_FIELDS_ARGS,
+ ErrorCode::RequiresDirectiveInFieldsArgs => &REQUIRES_DIRECTIVE_IN_FIELDS_ARGS,
+ ErrorCode::ExternalUnused => &EXTERNAL_UNUSED,
+ ErrorCode::ExternalCollisionWithAnotherDirective => {
+ &EXTERNAL_COLLISION_WITH_ANOTHER_DIRECTIVE
+ }
+ ErrorCode::TypeWithOnlyUnusedExternal => &TYPE_WITH_ONLY_UNUSED_EXTERNAL,
+ ErrorCode::ProvidesOnNonObjectField => &PROVIDES_ON_NON_OBJECT_FIELD,
+ ErrorCode::KeyInvalidFieldsType => &KEY_INVALID_FIELDS_TYPE,
+ ErrorCode::ProvidesInvalidFieldsType => &PROVIDES_INVALID_FIELDS_TYPE,
+ ErrorCode::RequiresInvalidFieldsType => &REQUIRES_INVALID_FIELDS_TYPE,
+ ErrorCode::KeyInvalidFields => &KEY_INVALID_FIELDS,
+ ErrorCode::ProvidesInvalidFields => &PROVIDES_INVALID_FIELDS,
+ ErrorCode::RequiresInvalidFields => &REQUIRES_INVALID_FIELDS,
+ ErrorCode::KeyFieldsSelectInvalidType => &KEY_FIELDS_SELECT_INVALID_TYPE,
+ ErrorCode::RootQueryUsed => &ROOT_QUERY_USED,
+ ErrorCode::RootMutationUsed => &ROOT_MUTATION_USED,
+ ErrorCode::RootSubscriptionUsed => &ROOT_SUBSCRIPTION_USED,
+ ErrorCode::InvalidSubgraphName => &INVALID_SUBGRAPH_NAME,
+ ErrorCode::NoQueries => &NO_QUERIES,
+ ErrorCode::InterfaceFieldNoImplem => &INTERFACE_FIELD_NO_IMPLEM,
+ ErrorCode::TypeKindMismatch => &TYPE_KIND_MISMATCH,
+ ErrorCode::ExternalTypeMismatch => &EXTERNAL_TYPE_MISMATCH,
+ ErrorCode::ExternalArgumentMissing => &EXTERNAL_ARGUMENT_MISSING,
+ ErrorCode::ExternalArgumentTypeMismatch => &EXTERNAL_ARGUMENT_TYPE_MISMATCH,
+ ErrorCode::ExternalArgumentDefaultMismatch => &EXTERNAL_ARGUMENT_DEFAULT_MISMATCH,
+ ErrorCode::ExternalOnInterface => &EXTERNAL_ON_INTERFACE,
+ ErrorCode::MergedDirectiveApplicationOnExternal => {
+ &MERGED_DIRECTIVE_APPLICATION_ON_EXTERNAL
+ }
+ ErrorCode::FieldTypeMismatch => &FIELD_TYPE_MISMATCH,
+ ErrorCode::FieldArgumentTypeMismatch => &FIELD_ARGUMENT_TYPE_MISMATCH,
+ ErrorCode::InputFieldDefaultMismatch => &INPUT_FIELD_DEFAULT_MISMATCH,
+ ErrorCode::FieldArgumentDefaultMismatch => &FIELD_ARGUMENT_DEFAULT_MISMATCH,
+ ErrorCode::ExtensionWithNoBase => &EXTENSION_WITH_NO_BASE,
+ ErrorCode::ExternalMissingOnBase => &EXTERNAL_MISSING_ON_BASE,
+ ErrorCode::InvalidFieldSharing => &INVALID_FIELD_SHARING,
+ ErrorCode::InvalidShareableUsage => &INVALID_SHAREABLE_USAGE,
+ ErrorCode::InvalidLinkDirectiveUsage => &INVALID_LINK_DIRECTIVE_USAGE,
+ ErrorCode::InvalidLinkIdentifier => &INVALID_LINK_IDENTIFIER,
+ ErrorCode::LinkImportNameMismatch => &LINK_IMPORT_NAME_MISMATCH,
+ ErrorCode::ReferencedInaccessible => &REFERENCED_INACCESSIBLE,
+ ErrorCode::DefaultValueUsesInaccessible => &DEFAULT_VALUE_USES_INACCESSIBLE,
+ ErrorCode::QueryRootTypeInaccessible => &QUERY_ROOT_TYPE_INACCESSIBLE,
+ ErrorCode::RequiredInaccessible => &REQUIRED_INACCESSIBLE,
+ ErrorCode::ImplementedByInaccessible => &IMPLEMENTED_BY_INACCESSIBLE,
+ ErrorCode::DisallowedInaccessible => &DISALLOWED_INACCESSIBLE,
+ ErrorCode::OnlyInaccessibleChildren => &ONLY_INACCESSIBLE_CHILDREN,
+ ErrorCode::RequiredInputFieldMissingInSomeSubgraph => {
+ &REQUIRED_INPUT_FIELD_MISSING_IN_SOME_SUBGRAPH
+ }
+ ErrorCode::RequiredArgumentMissingInSomeSubgraph => {
+ &REQUIRED_ARGUMENT_MISSING_IN_SOME_SUBGRAPH
+ }
+ ErrorCode::EmptyMergedInputType => &EMPTY_MERGED_INPUT_TYPE,
+ ErrorCode::EnumValueMismatch => &ENUM_VALUE_MISMATCH,
+ ErrorCode::EmptyMergedEnumType => &EMPTY_MERGED_ENUM_TYPE,
+ ErrorCode::ShareableHasMismatchedRuntimeTypes => {
+ &SHAREABLE_HAS_MISMATCHED_RUNTIME_TYPES
+ }
+ ErrorCode::SatisfiabilityError => &SATISFIABILITY_ERROR,
+ ErrorCode::OverrideFromSelfError => &OVERRIDE_FROM_SELF_ERROR,
+ ErrorCode::OverrideSourceHasOverride => &OVERRIDE_SOURCE_HAS_OVERRIDE,
+ ErrorCode::OverrideCollisionWithAnotherDirective => {
+ &OVERRIDE_COLLISION_WITH_ANOTHER_DIRECTIVE
+ }
+ ErrorCode::OverrideOnInterface => &OVERRIDE_ON_INTERFACE,
+ ErrorCode::UnsupportedFeature => &UNSUPPORTED_FEATURE,
+ ErrorCode::InvalidFederationSupergraph => &INVALID_FEDERATION_SUPERGRAPH,
+ ErrorCode::DownstreamServiceError => &DOWNSTREAM_SERVICE_ERROR,
+ ErrorCode::DirectiveCompositionError => &DIRECTIVE_COMPOSITION_ERROR,
+ ErrorCode::InterfaceObjectUsageError => &INTERFACE_OBJECT_USAGE_ERROR,
+ ErrorCode::InterfaceKeyNotOnImplementation => &INTERFACE_KEY_NOT_ON_IMPLEMENTATION,
+ ErrorCode::InterfaceKeyMissingImplementationType => {
+ &INTERFACE_KEY_MISSING_IMPLEMENTATION_TYPE
+ }
+ }
+ }
+}
diff --git a/apollo-federation/src/indented_display.rs b/apollo-federation/src/indented_display.rs
new file mode 100644
index 0000000000..920b21dbee
--- /dev/null
+++ b/apollo-federation/src/indented_display.rs
@@ -0,0 +1,65 @@
+use std::fmt;
+
+pub(crate) struct State<'fmt, 'fmt2> {
+ indent_level: usize,
+ output: &'fmt mut fmt::Formatter<'fmt2>,
+}
+
+impl<'a, 'b> State<'a, 'b> {
+ pub(crate) fn new(output: &'a mut fmt::Formatter<'b>) -> State<'a, 'b> {
+ Self {
+ indent_level: 0,
+ output,
+ }
+ }
+
+ pub(crate) fn indent_level(&self) -> usize {
+ self.indent_level
+ }
+
+ pub(crate) fn write(&mut self, value: T) -> fmt::Result {
+ write!(self.output, "{}", value)
+ }
+
+ pub(crate) fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> fmt::Result {
+ self.output.write_fmt(args)
+ }
+
+ pub(crate) fn new_line(&mut self) -> fmt::Result {
+ self.write("\n")?;
+ for _ in 0..self.indent_level {
+ self.write(" ")?
+ }
+ Ok(())
+ }
+
+ pub(crate) fn indent_no_new_line(&mut self) {
+ self.indent_level += 1;
+ }
+
+ pub(crate) fn indent(&mut self) -> fmt::Result {
+ self.indent_no_new_line();
+ self.new_line()
+ }
+
+ pub(crate) fn dedent(&mut self) -> fmt::Result {
+ self.indent_level -= 1;
+ self.new_line()
+ }
+}
+
+pub(crate) fn write_indented_lines(
+ state: &mut State<'_, '_>,
+ values: &[T],
+ mut write_line: impl FnMut(&mut State<'_, '_>, &T) -> fmt::Result,
+) -> fmt::Result {
+ if !values.is_empty() {
+ state.indent_no_new_line();
+ for value in values {
+ state.new_line()?;
+ write_line(state, value)?;
+ }
+ state.dedent()?;
+ }
+ Ok(())
+}
diff --git a/apollo-federation/src/lib.rs b/apollo-federation/src/lib.rs
new file mode 100644
index 0000000000..d6a87b11ea
--- /dev/null
+++ b/apollo-federation/src/lib.rs
@@ -0,0 +1,138 @@
+#![allow(dead_code)] // TODO: This is fine while we're iterating, but should be removed later.
+
+mod api_schema;
+mod compat;
+pub mod error;
+mod indented_display;
+pub mod link;
+pub mod merge;
+pub mod query_graph;
+pub mod query_plan;
+pub mod schema;
+pub mod subgraph;
+
+use apollo_compiler::validation::Valid;
+use apollo_compiler::NodeStr;
+use apollo_compiler::Schema;
+use link::join_spec_definition::JOIN_VERSIONS;
+use schema::FederationSchema;
+
+pub use crate::api_schema::ApiSchemaOptions;
+use crate::error::FederationError;
+use crate::error::SingleFederationError;
+use crate::link::join_spec_definition::JoinSpecDefinition;
+use crate::link::link_spec_definition::LinkSpecDefinition;
+use crate::link::spec::Identity;
+use crate::link::spec_definition::SpecDefinitions;
+use crate::merge::merge_subgraphs;
+use crate::merge::MergeFailure;
+pub use crate::query_graph::extract_subgraphs_from_supergraph::ValidFederationSubgraph;
+pub use crate::query_graph::extract_subgraphs_from_supergraph::ValidFederationSubgraphs;
+use crate::schema::ValidFederationSchema;
+use crate::subgraph::ValidSubgraph;
+
+pub(crate) type SupergraphSpecs = (&'static LinkSpecDefinition, &'static JoinSpecDefinition);
+
+pub(crate) fn validate_supergraph_for_query_planning(
+ supergraph_schema: &FederationSchema,
+) -> Result {
+ validate_supergraph(supergraph_schema, &JOIN_VERSIONS)
+}
+
+pub(crate) fn validate_supergraph_for_non_query_planning(
+ supergraph_schema: &FederationSchema,
+) -> Result {
+ validate_supergraph(
+ supergraph_schema,
+ &link::join_spec_definition::NON_QUERY_PLANNING_JOIN_VERSIONS,
+ )
+}
+
+/// Checks that required supergraph directives are in the schema, and returns which ones were used.
+pub(crate) fn validate_supergraph(
+ supergraph_schema: &FederationSchema,
+ join_versions: &'static SpecDefinitions,
+) -> Result {
+ let Some(metadata) = supergraph_schema.metadata() else {
+ return Err(SingleFederationError::InvalidFederationSupergraph {
+ message: "Invalid supergraph: must be a core schema".to_owned(),
+ }
+ .into());
+ };
+ let link_spec_definition = metadata.link_spec_definition()?;
+ let Some(join_link) = metadata.for_identity(&Identity::join_identity()) else {
+ return Err(SingleFederationError::InvalidFederationSupergraph {
+ message: "Invalid supergraph: must use the join spec".to_owned(),
+ }
+ .into());
+ };
+ let Some(join_spec_definition) = join_versions.find(&join_link.url.version) else {
+ return Err(SingleFederationError::InvalidFederationSupergraph {
+ message: format!(
+ "Invalid supergraph: uses unsupported join spec version {} (supported versions: {})",
+ join_link.url.version,
+ join_versions.versions().map(|v| v.to_string()).collect::>().join(", "),
+ ),
+ }.into());
+ };
+ Ok((link_spec_definition, join_spec_definition))
+}
+
+pub struct Supergraph {
+ pub schema: ValidFederationSchema,
+}
+
+impl Supergraph {
+ pub fn new(schema_str: &str) -> Result {
+ let schema = Schema::parse_and_validate(schema_str, "schema.graphql")?;
+ Self::from_schema(schema)
+ }
+
+ pub fn from_schema(schema: Valid) -> Result {
+ let schema = schema.into_inner();
+ let schema = FederationSchema::new(schema)?;
+
+ let _ = validate_supergraph_for_non_query_planning(&schema)?;
+
+ Ok(Self {
+ // We know it's valid because the input was.
+ schema: schema.assume_valid()?,
+ })
+ }
+
+ pub fn compose(subgraphs: Vec<&ValidSubgraph>) -> Result {
+ let schema = merge_subgraphs(subgraphs)?.schema;
+ Ok(Self {
+ schema: ValidFederationSchema::new(schema)
+ .map_err(|err| todo!("missing error handling: {err}"))?,
+ })
+ }
+
+ /// Generates an API Schema from this supergraph schema. The API Schema represents the combined
+ /// API of the supergraph that's visible to end users.
+ pub fn to_api_schema(
+ &self,
+ options: ApiSchemaOptions,
+ ) -> Result {
+ api_schema::to_api_schema(self.schema.clone(), options)
+ }
+
+ pub fn extract_subgraphs(&self) -> Result {
+ crate::query_graph::extract_subgraphs_from_supergraph::extract_subgraphs_from_supergraph(
+ &self.schema,
+ None,
+ )
+ }
+}
+
+const _: () = {
+ const fn assert_thread_safe() {}
+
+ assert_thread_safe::();
+ assert_thread_safe::();
+};
+
+/// Returns if the type of the node is a scalar or enum.
+pub(crate) fn is_leaf_type(schema: &Schema, ty: &NodeStr) -> bool {
+ schema.get_scalar(ty).is_some() || schema.get_enum(ty).is_some()
+}
diff --git a/apollo-federation/src/link/argument.rs b/apollo-federation/src/link/argument.rs
new file mode 100644
index 0000000000..cbaa936d39
--- /dev/null
+++ b/apollo-federation/src/link/argument.rs
@@ -0,0 +1,167 @@
+use std::ops::Deref;
+
+use apollo_compiler::ast::Value;
+use apollo_compiler::schema::Directive;
+use apollo_compiler::schema::Name;
+use apollo_compiler::Node;
+use apollo_compiler::NodeStr;
+
+use crate::error::FederationError;
+use crate::error::SingleFederationError;
+use crate::link::graphql_definition::BooleanOrVariable;
+
+pub(crate) fn directive_optional_enum_argument(
+ application: &Node,
+ name: &Name,
+) -> Result