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, FederationError> { + match application.argument_by_name(name) { + Some(value) => match value.deref() { + Value::Enum(name) => Ok(Some(name.clone())), + Value::Null => Ok(None), + _ => Err(SingleFederationError::Internal { + message: format!( + "Argument \"{}\" of directive \"@{}\" must be an enum value.", + name, application.name + ), + } + .into()), + }, + None => Ok(None), + } +} + +pub(crate) fn directive_required_enum_argument( + application: &Node, + name: &Name, +) -> Result { + directive_optional_enum_argument(application, name)?.ok_or_else(|| { + SingleFederationError::Internal { + message: format!( + "Required argument \"{}\" of directive \"@{}\" was not present.", + name, application.name + ), + } + .into() + }) +} + +pub(crate) fn directive_optional_string_argument( + application: &Node, + name: &Name, +) -> Result, FederationError> { + match application.argument_by_name(name) { + Some(value) => match value.deref() { + Value::String(name) => Ok(Some(name.clone())), + Value::Null => Ok(None), + _ => Err(SingleFederationError::Internal { + message: format!( + "Argument \"{}\" of directive \"@{}\" must be a string.", + name, application.name + ), + } + .into()), + }, + None => Ok(None), + } +} + +pub(crate) fn directive_required_string_argument( + application: &Node, + name: &Name, +) -> Result { + directive_optional_string_argument(application, name)?.ok_or_else(|| { + SingleFederationError::Internal { + message: format!( + "Required argument \"{}\" of directive \"@{}\" was not present.", + name, application.name + ), + } + .into() + }) +} + +pub(crate) fn directive_optional_fieldset_argument( + application: &Node, + name: &Name, +) -> Result, FederationError> { + match application.argument_by_name(name) { + Some(value) => match value.deref() { + Value::String(name) => Ok(Some(name.clone())), + Value::Null => Ok(None), + _ => Err(SingleFederationError::Internal { + message: format!("Invalid value for argument \"{}\": must be a string.", name), + } + .into()), + }, + None => Ok(None), + } +} + +pub(crate) fn directive_required_fieldset_argument( + application: &Node, + name: &Name, +) -> Result { + directive_optional_fieldset_argument(application, name)?.ok_or_else(|| { + SingleFederationError::Internal { + message: format!( + "Required argument \"{}\" of directive \"@{}\" was not present.", + name, application.name + ), + } + .into() + }) +} + +pub(crate) fn directive_optional_boolean_argument( + application: &Node, + name: &Name, +) -> Result, FederationError> { + match application.argument_by_name(name) { + Some(value) => match value.deref() { + Value::Boolean(value) => Ok(Some(*value)), + Value::Null => Ok(None), + _ => Err(SingleFederationError::Internal { + message: format!( + "Argument \"{}\" of directive \"@{}\" must be a boolean.", + name, application.name + ), + } + .into()), + }, + None => Ok(None), + } +} + +#[allow(dead_code)] +pub(crate) fn directive_required_boolean_argument( + application: &Node, + name: &Name, +) -> Result { + directive_optional_boolean_argument(application, name)?.ok_or_else(|| { + SingleFederationError::Internal { + message: format!( + "Required argument \"{}\" of directive \"@{}\" was not present.", + name, application.name + ), + } + .into() + }) +} + +pub(crate) fn directive_optional_variable_boolean_argument( + application: &Node, + name: &Name, +) -> Result, FederationError> { + match application.argument_by_name(name) { + Some(value) => match value.deref() { + Value::Variable(name) => Ok(Some(BooleanOrVariable::Variable(name.clone()))), + Value::Boolean(value) => Ok(Some(BooleanOrVariable::Boolean(*value))), + Value::Null => Ok(None), + _ => Err(FederationError::internal(format!( + "Argument \"{}\" of directive \"@{}\" must be a boolean.", + name, application.name + ))), + }, + None => Ok(None), + } +} diff --git a/apollo-federation/src/link/database.rs b/apollo-federation/src/link/database.rs new file mode 100644 index 0000000000..e15b80f10d --- /dev/null +++ b/apollo-federation/src/link/database.rs @@ -0,0 +1,564 @@ +use std::borrow::Cow; +use std::collections::HashMap; +use std::sync::Arc; + +use apollo_compiler::ast::Directive; +use apollo_compiler::ast::DirectiveLocation; +use apollo_compiler::schema::DirectiveDefinition; +use apollo_compiler::ty; +use apollo_compiler::Schema; + +use crate::link::spec::Identity; +use crate::link::spec::Url; +use crate::link::Link; +use crate::link::LinkError; +use crate::link::LinksMetadata; +use crate::link::DEFAULT_LINK_NAME; + +/// Extract @link metadata from a schema. +pub fn links_metadata(schema: &Schema) -> Result, LinkError> { + // This finds "bootstrap" uses of @link / @core regardless of order. By spec, + // the bootstrap directive application must be the first application of @link / @core, but + // this was not enforced by the JS implementation, so we match it for backward compatibility. + let mut bootstrap_directives = schema + .schema_definition + .directives + .iter() + .filter(|d| is_bootstrap_directive(schema, d)); + let Some(bootstrap_directive) = bootstrap_directives.next() else { + return Ok(None); + }; + // There must be exactly one bootstrap directive. + if let Some(extraneous_directive) = bootstrap_directives.next() { + return Err(LinkError::BootstrapError(format!( + "the @link specification itself (\"{}\") is applied multiple times", + extraneous_directive + .argument_by_name("url") + // XXX(@goto-bus-stop): @core compatibility is primarily to support old tests in other projects, + // and should be removed when those are updated. + .or(extraneous_directive.argument_by_name("feature")) + .and_then(|value| value.as_str().map(Cow::Borrowed)) + .unwrap_or_else(|| Cow::Owned(Identity::link_identity().to_string())) + ))); + } + + // At this point, we know this schema uses "our" @link. So we now "just" want to validate + // all of the @link usages (starting with the bootstrapping one) and extract their metadata. + let link_name_in_schema = &bootstrap_directive.name; + let mut links = Vec::new(); + let mut by_identity = HashMap::new(); + let mut by_name_in_schema = HashMap::new(); + let mut types_by_imported_name = HashMap::new(); + let mut directives_by_imported_name = HashMap::new(); + let link_applications = schema + .schema_definition + .directives + .iter() + .filter(|d| d.name == *link_name_in_schema); + for application in link_applications { + let link = Arc::new(Link::from_directive_application(application)?); + links.push(Arc::clone(&link)); + if by_identity + .insert(link.url.identity.clone(), Arc::clone(&link)) + .is_some() + { + // TODO: we may want to lessen that limitation at some point. Including the same feature for 2 different major versions should be ok. + return Err(LinkError::BootstrapError(format!( + "duplicate @link inclusion of specification \"{}\"", + link.url.identity + ))); + } + let name_in_schema = link.spec_name_in_schema(); + if let Some(other) = by_name_in_schema.insert(name_in_schema.clone(), Arc::clone(&link)) { + return Err(LinkError::BootstrapError(format!( + "name conflict: {} and {} are imported under the same name (consider using the `@link(as:)` argument to disambiguate)", + other.url, link.url, + ))); + } + } + + // We do a 2nd pass to collect and validate all the imports (it's a separate path so we + // know all the names of the spec linked in the schema). + for link in &links { + for import in &link.imports { + let imported_name = import.imported_name(); + let element_map = if import.is_directive { + // the name of each spec (in the schema) acts as an implicit import for a + // directive of the same name. So one cannot import a direcitive with the + // same name than a linked spec. + if let Some(other) = by_name_in_schema.get(imported_name) { + if !Arc::ptr_eq(other, link) { + return Err(LinkError::BootstrapError(format!( + "import for '{}' of {} conflicts with spec {}", + import.imported_display_name(), + link.url, + other.url + ))); + } + } + &mut directives_by_imported_name + } else { + &mut types_by_imported_name + }; + if let Some((other_link, _)) = element_map.insert( + imported_name.clone(), + (Arc::clone(link), Arc::clone(import)), + ) { + return Err(LinkError::BootstrapError(format!( + "name conflict: both {} and {} import {}", + link.url, + other_link.url, + import.imported_display_name() + ))); + } + } + } + + Ok(Some(LinksMetadata { + links, + by_identity, + by_name_in_schema, + types_by_imported_name, + directives_by_imported_name, + })) +} + +/// Returns true if the given definition matches the @link definition. +/// +/// Either of these definitions are accepted: +/// ```graphql +/// directive @_ANY_NAME_(url: String!, as: String) repeatable on SCHEMA +/// directive @_ANY_NAME_(url: String, as: String) repeatable on SCHEMA +/// ``` +fn is_link_directive_definition(definition: &DirectiveDefinition) -> bool { + definition.repeatable + && definition.locations == [DirectiveLocation::Schema] + && definition.argument_by_name("url").is_some_and(|argument| { + // The "true" type of `url` in the @link spec is actually `String` (nullable), and this + // for future-proofing reasons (the idea was that we may introduce later other + // ways to identify specs that are not urls). But we allow the definition to + // have a non-nullable type both for convenience and because some early + // federation previews actually generated that. + *argument.ty == ty!(String!) || *argument.ty == ty!(String) + }) + && definition + .argument_by_name("as") + .is_some_and(|argument| *argument.ty == ty!(String)) +} + +/// Returns true if the given definition matches the @core definition. +/// +/// Either of these definitions are accepted: +/// ```graphql +/// directive @_ANY_NAME_(feature: String!, as: String) repeatable on SCHEMA +/// directive @_ANY_NAME_(feature: String, as: String) repeatable on SCHEMA +/// directive @_ANY_NAME_(feature: String!) repeatable on SCHEMA +/// directive @_ANY_NAME_(feature: String) repeatable on SCHEMA +/// ``` +fn is_core_directive_definition(definition: &DirectiveDefinition) -> bool { + // XXX(@goto-bus-stop): @core compatibility is primarily to support old tests--should be + // removed when those are updated. + definition.repeatable + && definition.locations == [DirectiveLocation::Schema] + && definition + .argument_by_name("feature") + .is_some_and(|argument| { + // The "true" type of `url` in the @core spec is actually `String` (nullable), and this + // for future-proofing reasons (the idea was that we may introduce later other + // ways to identify specs that are not urls). But we allow the definition to + // have a non-nullable type both for convenience and because some early + // federation previews actually generated that. + *argument.ty == ty!(String!) || *argument.ty == ty!(String) + }) + && definition + .argument_by_name("as") + // Definition may be omitted in old graphs + .map_or(true, |argument| *argument.ty == ty!(String)) +} + +/// Returns whether a given directive is the @link or @core directive that imports the @link or +/// @core spec. +fn is_bootstrap_directive(schema: &Schema, directive: &Directive) -> bool { + let Some(definition) = schema.directive_definitions.get(&directive.name) else { + return false; + }; + if is_link_directive_definition(definition) { + if let Some(url) = directive + .argument_by_name("url") + .and_then(|value| value.as_str()) + { + let url = url.parse::(); + let default_link_name = DEFAULT_LINK_NAME; + let expected_name = directive + .argument_by_name("as") + .and_then(|value| value.as_str()) + .unwrap_or(default_link_name.as_str()); + return url.map_or(false, |url| { + url.identity == Identity::link_identity() && directive.name == expected_name + }); + } + } else if is_core_directive_definition(definition) { + // XXX(@goto-bus-stop): @core compatibility is primarily to support old tests--should be + // removed when those are updated. + if let Some(url) = directive + .argument_by_name("feature") + .and_then(|value| value.as_str()) + { + let url = url.parse::(); + let expected_name = directive + .argument_by_name("as") + .and_then(|value| value.as_str()) + .unwrap_or("core"); + return url.map_or(false, |url| { + url.identity == Identity::core_identity() && directive.name == expected_name + }); + } + }; + false +} + +#[cfg(test)] +mod tests { + use apollo_compiler::name; + + use super::*; + use crate::link::spec::Version; + use crate::link::spec::APOLLO_SPEC_DOMAIN; + use crate::link::Import; + use crate::link::Purpose; + + #[test] + fn explicit_root_directive_import() -> Result<(), LinkError> { + let schema = r#" + extend schema + @link(url: "https://specs.apollo.dev/link/v1.0", import: ["Import"]) + @link(url: "https://specs.apollo.dev/inaccessible/v0.2", import: ["@inaccessible"]) + + type Query { x: Int } + + enum link__Purpose { + SECURITY + EXECUTION + } + + scalar Import + + directive @link(url: String, as: String, import: [Import], for: link__Purpose) repeatable on SCHEMA + "#; + + let schema = Schema::parse(schema, "root_directive.graphqls").unwrap(); + + let meta = links_metadata(&schema)?; + let meta = meta.expect("should have metadata"); + + assert!(meta + .source_link_of_directive(&name!("inaccessible")) + .is_some()); + + Ok(()) + } + + #[test] + fn renamed_link_directive() -> Result<(), LinkError> { + let schema = r#" + extend schema + @lonk(url: "https://specs.apollo.dev/link/v1.0", as: "lonk") + @lonk(url: "https://specs.apollo.dev/inaccessible/v0.2") + + type Query { x: Int } + + enum lonk__Purpose { + SECURITY + EXECUTION + } + + scalar lonk__Import + + directive @lonk(url: String!, as: String, import: [lonk__Import], for: lonk__Purpose) repeatable on SCHEMA + "#; + + let schema = Schema::parse(schema, "lonk.graphqls").unwrap(); + + let meta = links_metadata(&schema)?.expect("should have metadata"); + assert!(meta + .source_link_of_directive(&name!("inaccessible")) + .is_some()); + + Ok(()) + } + + #[test] + fn renamed_core_directive() -> Result<(), LinkError> { + let schema = r#" + extend schema + @care(feature: "https://specs.apollo.dev/core/v0.2", as: "care") + @care(feature: "https://specs.apollo.dev/join/v0.2", for: EXECUTION) + + directive @care(feature: String!, as: String, for: core__Purpose) repeatable on SCHEMA + directive @join__field(graph: join__Graph!, requires: join__FieldSet, provides: join__FieldSet, type: String, external: Boolean) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION + directive @join__graph(name: String!, url: String!) on ENUM_VALUE + directive @join__implements(graph: join__Graph!, interface: String!) repeatable on OBJECT | INTERFACE + directive @join__type(graph: join__Graph!, key: join__FieldSet, extension: Boolean! = false, resolvable: Boolean! = true) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR + + type Query { x: Int } + + enum care__Purpose { + SECURITY + EXECUTION + } + + scalar care__Import + + scalar join__FieldSet + + enum join__Graph { + USERS @join__graph(name: "users", url: "http://localhost:4001") + } + "#; + + let schema = Schema::parse(schema, "care.graphqls").unwrap(); + + let meta = links_metadata(&schema)?.expect("should have metadata"); + assert!(meta + .source_link_of_directive(&name!("join__graph")) + .is_some()); + + Ok(()) + } + + #[test] + fn url_syntax() -> Result<(), LinkError> { + let schema = r#" + extend schema + @link(url: "https://specs.apollo.dev/link/v1.0") + @link(url: "https://specs.apollo.dev/join/v0.3", for: EXECUTION) + @link(url: "https://example.com/my-directive/v1.0", import: ["@myDirective"]) + + type Query { x: Int } + + directive @myDirective on FIELD_DEFINITION | ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION + + directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE + + directive @join__field(graph: join__Graph, requires: join__FieldSet, provides: join__FieldSet, type: String, external: Boolean, override: String, usedOverridden: Boolean) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION + + directive @join__graph(name: String!, url: String!) on ENUM_VALUE + + directive @join__implements(graph: join__Graph!, interface: String!) repeatable on OBJECT | INTERFACE + + directive @join__type(graph: join__Graph!, key: join__FieldSet, extension: Boolean! = false, resolvable: Boolean! = true, isInterfaceObject: Boolean! = false) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR + + directive @join__unionMember(graph: join__Graph!, member: String!) repeatable on UNION + + directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA + "#; + + let schema = Schema::parse(schema, "url_dash.graphqls").unwrap(); + + let meta = links_metadata(&schema)?; + let meta = meta.expect("should have metadata"); + + assert!(meta + .source_link_of_directive(&name!("myDirective")) + .is_some()); + + Ok(()) + } + + #[test] + fn computes_link_metadata() { + let schema = r#" + extend schema + @link(url: "https://specs.apollo.dev/link/v1.0", import: ["Import"]) + @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key", { name: "@tag", as: "@myTag" }]) + @link(url: "https://custom.com/someSpec/v0.2", as: "mySpec") + @link(url: "https://megacorp.com/auth/v1.0", for: SECURITY) + + type Query { + x: Int + } + + enum link__Purpose { + SECURITY + EXECUTION + } + + scalar Import + + directive @link(url: String, as: String, import: [Import], for: link__Purpose) repeatable on SCHEMA + "#; + + let schema = Schema::parse(schema, "testSchema").unwrap(); + + let meta = links_metadata(&schema) + // TODO: error handling? + .unwrap() + .unwrap(); + let names_in_schema = meta + .all_links() + .iter() + .map(|l| l.spec_name_in_schema()) + .collect::>(); + assert_eq!(names_in_schema.len(), 4); + assert_eq!(names_in_schema[0], "link"); + assert_eq!(names_in_schema[1], "federation"); + assert_eq!(names_in_schema[2], "mySpec"); + assert_eq!(names_in_schema[3], "auth"); + + let link_spec = meta.for_identity(&Identity::link_identity()).unwrap(); + assert_eq!( + link_spec.imports.first().unwrap().as_ref(), + &Import { + element: name!("Import"), + is_directive: false, + alias: None + } + ); + + let fed_spec = meta + .for_identity(&Identity { + domain: APOLLO_SPEC_DOMAIN.to_string(), + name: name!("federation"), + }) + .unwrap(); + assert_eq!(fed_spec.url.version, Version { major: 2, minor: 3 }); + assert_eq!(fed_spec.purpose, None); + + let imports = &fed_spec.imports; + assert_eq!(imports.len(), 2); + assert_eq!( + imports.first().unwrap().as_ref(), + &Import { + element: name!("key"), + is_directive: true, + alias: None + } + ); + assert_eq!( + imports.get(1).unwrap().as_ref(), + &Import { + element: name!("tag"), + is_directive: true, + alias: Some(name!("myTag")) + } + ); + + let auth_spec = meta + .for_identity(&Identity { + domain: "https://megacorp.com".to_string(), + name: name!("auth"), + }) + .unwrap(); + assert_eq!(auth_spec.purpose, Some(Purpose::SECURITY)); + + let import_source = meta.source_link_of_type(&name!("Import")).unwrap(); + assert_eq!(import_source.link.url.identity.name, "link"); + assert!(!import_source.import.as_ref().unwrap().is_directive); + assert_eq!(import_source.import.as_ref().unwrap().alias, None); + + // Purpose is not imported, so it should only be accessible in fql form + assert!(meta.source_link_of_type(&name!("Purpose")).is_none()); + + let purpose_source = meta.source_link_of_type(&name!("link__Purpose")).unwrap(); + assert_eq!(purpose_source.link.url.identity.name, "link"); + assert_eq!(purpose_source.import, None); + + let key_source = meta.source_link_of_directive(&name!("key")).unwrap(); + assert_eq!(key_source.link.url.identity.name, "federation"); + assert!(key_source.import.as_ref().unwrap().is_directive); + assert_eq!(key_source.import.as_ref().unwrap().alias, None); + + // tag is imported under an alias, so "tag" itself should not match + assert!(meta.source_link_of_directive(&name!("tag")).is_none()); + + let tag_source = meta.source_link_of_directive(&name!("myTag")).unwrap(); + assert_eq!(tag_source.link.url.identity.name, "federation"); + assert_eq!(tag_source.import.as_ref().unwrap().element, "tag"); + assert!(tag_source.import.as_ref().unwrap().is_directive); + assert_eq!( + tag_source.import.as_ref().unwrap().alias, + Some(name!("myTag")) + ); + } + + mod link_import { + use super::*; + + #[test] + fn errors_on_malformed_values() { + let schema = r#" + extend schema @link(url: "https://specs.apollo.dev/link/v1.0") + extend schema @link( + url: "https://specs.apollo.dev/federation/v2.0", + import: [ + 2, + { foo: "bar" }, + { name: "@key", badName: "foo"}, + { name: 42 }, + { as: "bar" }, + ] + ) + + type Query { + q: Int + } + + directive @link(url: String, as: String, import: [Import], for: link__Purpose) repeatable on SCHEMA + "#; + + let schema = Schema::parse(schema, "testSchema").unwrap(); + let errors = links_metadata(&schema).expect_err("should error"); + // TODO Multiple errors + insta::assert_snapshot!(errors, @r###"Invalid use of @link in schema: invalid sub-value for @link(import:) argument: values should be either strings or input object values of the form { name: "", as: "" }."###); + } + + #[test] + fn errors_on_mismatch_between_name_and_alias() { + let schema = r#" + extend schema @link(url: "https://specs.apollo.dev/link/v1.0") + extend schema @link( + url: "https://specs.apollo.dev/federation/v2.0", + import: [ + { name: "@key", as: "myKey" }, + { name: "FieldSet", as: "@fieldSet" }, + ] + ) + + type Query { + q: Int + } + + directive @link(url: String, as: String, import: [Import], for: link__Purpose) repeatable on SCHEMA + "#; + + let schema = Schema::parse(schema, "testSchema").unwrap(); + let errors = links_metadata(&schema).expect_err("should error"); + // TODO Multiple errors + insta::assert_snapshot!(errors, @"Invalid use of @link in schema: invalid alias 'myKey' for import name '@key': should start with '@' since the imported name does"); + } + + // TODO Implement + /* + #[test] + fn errors_on_importing_unknown_elements_for_known_features() { + let schema = r#" + extend schema @link(url: "https://specs.apollo.dev/link/v1.0") + extend schema @link( + url: "https://specs.apollo.dev/federation/v2.0", + import: [ "@foo", "key", { name: "@sharable" } ] + ) + + type Query { + q: Int + } + + directive @link(url: String, as: String, import: [Import], for: link__Purpose) repeatable on SCHEMA + "#; + + let schema = Schema::parse(schema, "testSchema").unwrap(); + let errors = links_metadata(&schema).expect_err("should error"); + insta::assert_snapshot!(errors, @""); + } + */ + } +} diff --git a/apollo-federation/src/link/federation_spec_definition.rs b/apollo-federation/src/link/federation_spec_definition.rs new file mode 100644 index 0000000000..2b2c3fc5e8 --- /dev/null +++ b/apollo-federation/src/link/federation_spec_definition.rs @@ -0,0 +1,426 @@ +use apollo_compiler::ast::Argument; +use apollo_compiler::name; +use apollo_compiler::schema::Directive; +use apollo_compiler::schema::DirectiveDefinition; +use apollo_compiler::schema::ExtendedType; +use apollo_compiler::schema::Name; +use apollo_compiler::schema::UnionType; +use apollo_compiler::schema::Value; +use apollo_compiler::Node; +use apollo_compiler::NodeStr; +use lazy_static::lazy_static; + +use crate::error::FederationError; +use crate::error::SingleFederationError; +use crate::link::argument::directive_optional_boolean_argument; +use crate::link::argument::directive_required_fieldset_argument; +use crate::link::spec::Identity; +use crate::link::spec::Url; +use crate::link::spec::Version; +use crate::link::spec_definition::SpecDefinition; +use crate::link::spec_definition::SpecDefinitions; +use crate::schema::FederationSchema; + +pub(crate) const FEDERATION_ENTITY_TYPE_NAME_IN_SPEC: Name = name!("_Entity"); +pub(crate) const FEDERATION_KEY_DIRECTIVE_NAME_IN_SPEC: Name = name!("key"); +pub(crate) const FEDERATION_INTERFACEOBJECT_DIRECTIVE_NAME_IN_SPEC: Name = name!("interfaceObject"); +pub(crate) const FEDERATION_EXTENDS_DIRECTIVE_NAME_IN_SPEC: Name = name!("extends"); +pub(crate) const FEDERATION_EXTERNAL_DIRECTIVE_NAME_IN_SPEC: Name = name!("external"); +pub(crate) const FEDERATION_REQUIRES_DIRECTIVE_NAME_IN_SPEC: Name = name!("requires"); +pub(crate) const FEDERATION_PROVIDES_DIRECTIVE_NAME_IN_SPEC: Name = name!("provides"); +pub(crate) const FEDERATION_SHAREABLE_DIRECTIVE_NAME_IN_SPEC: Name = name!("shareable"); +pub(crate) const FEDERATION_OVERRIDE_DIRECTIVE_NAME_IN_SPEC: Name = name!("override"); + +pub(crate) const FEDERATION_FIELDS_ARGUMENT_NAME: Name = name!("fields"); +pub(crate) const FEDERATION_RESOLVABLE_ARGUMENT_NAME: Name = name!("resolvable"); +pub(crate) const FEDERATION_REASON_ARGUMENT_NAME: Name = name!("reason"); +pub(crate) const FEDERATION_FROM_ARGUMENT_NAME: Name = name!("from"); + +pub(crate) struct KeyDirectiveArguments { + pub(crate) fields: NodeStr, + pub(crate) resolvable: bool, +} + +pub(crate) struct RequiresDirectiveArguments { + pub(crate) fields: NodeStr, +} + +pub(crate) struct ProvidesDirectiveArguments { + pub(crate) fields: NodeStr, +} + +#[derive(Debug)] +pub(crate) struct FederationSpecDefinition { + url: Url, +} + +impl FederationSpecDefinition { + pub(crate) fn new(version: Version) -> Self { + Self { + url: Url { + identity: Identity::federation_identity(), + version, + }, + } + } + + pub(crate) fn entity_type_definition<'schema>( + &self, + schema: &'schema FederationSchema, + ) -> Result>, FederationError> { + // Note that the _Entity type is special in that: + // 1. Spec renaming doesn't take place for it (there's no prefixing or importing needed), + // in order to maintain backwards compatibility with Fed 1. + // 2. Its presence is optional; if absent, it means the subgraph has no resolvable keys. + match schema + .schema() + .types + .get(&FEDERATION_ENTITY_TYPE_NAME_IN_SPEC) + { + Some(ExtendedType::Union(type_)) => Ok(Some(type_)), + None => Ok(None), + _ => Err(SingleFederationError::Internal { + message: format!( + "Unexpectedly found non-union for federation spec's \"{}\" type definition", + FEDERATION_ENTITY_TYPE_NAME_IN_SPEC + ), + } + .into()), + } + } + + pub(crate) fn key_directive_definition<'schema>( + &self, + schema: &'schema FederationSchema, + ) -> Result<&'schema Node, FederationError> { + self.directive_definition(schema, &FEDERATION_KEY_DIRECTIVE_NAME_IN_SPEC)? + .ok_or_else(|| { + SingleFederationError::Internal { + message: format!( + "Unexpectedly could not find federation spec's \"@{}\" directive definition", + FEDERATION_KEY_DIRECTIVE_NAME_IN_SPEC + ), + } + .into() + }) + } + + pub(crate) fn key_directive( + &self, + schema: &FederationSchema, + fields: NodeStr, + resolvable: bool, + ) -> Result { + let name_in_schema = self + .directive_name_in_schema(schema, &FEDERATION_KEY_DIRECTIVE_NAME_IN_SPEC)? + .ok_or_else(|| SingleFederationError::Internal { + message: "Unexpectedly could not find federation spec in schema".to_owned(), + })?; + Ok(Directive { + name: name_in_schema, + arguments: vec![ + Node::new(Argument { + name: FEDERATION_FIELDS_ARGUMENT_NAME, + value: Node::new(Value::String(fields)), + }), + Node::new(Argument { + name: FEDERATION_RESOLVABLE_ARGUMENT_NAME, + value: Node::new(Value::Boolean(resolvable)), + }), + ], + }) + } + + pub(crate) fn key_directive_arguments( + &self, + application: &Node, + ) -> Result { + Ok(KeyDirectiveArguments { + fields: directive_required_fieldset_argument( + application, + &FEDERATION_FIELDS_ARGUMENT_NAME, + )?, + resolvable: directive_optional_boolean_argument( + application, + &FEDERATION_RESOLVABLE_ARGUMENT_NAME, + )? + .unwrap_or(false), + }) + } + + pub(crate) fn interface_object_directive_definition<'schema>( + &self, + schema: &'schema FederationSchema, + ) -> Result>, FederationError> { + if *self.version() < (Version { major: 2, minor: 3 }) { + return Ok(None); + } + self.directive_definition(schema, &FEDERATION_INTERFACEOBJECT_DIRECTIVE_NAME_IN_SPEC)? + .ok_or_else(|| { + SingleFederationError::Internal { + message: format!( + "Unexpectedly could not find federation spec's \"@{}\" directive definition", + FEDERATION_INTERFACEOBJECT_DIRECTIVE_NAME_IN_SPEC + ), + }.into() + }) + .map(Some) + } + + pub(crate) fn interface_object_directive( + &self, + schema: &FederationSchema, + ) -> Result { + if *self.version() < (Version { major: 2, minor: 3 }) { + return Err(SingleFederationError::Internal { + message: "Must be using federation >= v2.3 to use interface object".to_owned(), + } + .into()); + } + let name_in_schema = self + .directive_name_in_schema(schema, &FEDERATION_INTERFACEOBJECT_DIRECTIVE_NAME_IN_SPEC)? + .ok_or_else(|| SingleFederationError::Internal { + message: "Unexpectedly could not find federation spec in schema".to_owned(), + })?; + Ok(Directive { + name: name_in_schema, + arguments: Vec::new(), + }) + } + + pub(crate) fn extends_directive_definition<'schema>( + &self, + schema: &'schema FederationSchema, + ) -> Result<&'schema Node, FederationError> { + self.directive_definition(schema, &FEDERATION_EXTENDS_DIRECTIVE_NAME_IN_SPEC)? + .ok_or_else(|| { + FederationError::internal(format!( + "Unexpectedly could not find federation spec's \"@{}\" directive definition", + FEDERATION_EXTENDS_DIRECTIVE_NAME_IN_SPEC + )) + }) + } + + pub(crate) fn external_directive_definition<'schema>( + &self, + schema: &'schema FederationSchema, + ) -> Result<&'schema Node, FederationError> { + self.directive_definition(schema, &FEDERATION_EXTERNAL_DIRECTIVE_NAME_IN_SPEC)? + .ok_or_else(|| { + SingleFederationError::Internal { + message: format!( + "Unexpectedly could not find federation spec's \"@{}\" directive definition", + FEDERATION_EXTERNAL_DIRECTIVE_NAME_IN_SPEC + ), + }.into() + }) + } + + pub(crate) fn external_directive( + &self, + schema: &FederationSchema, + reason: Option, + ) -> Result { + let name_in_schema = self + .directive_name_in_schema(schema, &FEDERATION_EXTERNAL_DIRECTIVE_NAME_IN_SPEC)? + .ok_or_else(|| SingleFederationError::Internal { + message: "Unexpectedly could not find federation spec in schema".to_owned(), + })?; + let mut arguments = Vec::new(); + if let Some(reason) = reason { + arguments.push(Node::new(Argument { + name: FEDERATION_REASON_ARGUMENT_NAME, + value: Node::new(Value::String(reason)), + })); + } + Ok(Directive { + name: name_in_schema, + arguments, + }) + } + + pub(crate) fn requires_directive_definition<'schema>( + &self, + schema: &'schema FederationSchema, + ) -> Result<&'schema Node, FederationError> { + self.directive_definition(schema, &FEDERATION_REQUIRES_DIRECTIVE_NAME_IN_SPEC)? + .ok_or_else(|| { + SingleFederationError::Internal { + message: format!( + "Unexpectedly could not find federation spec's \"@{}\" directive definition", + FEDERATION_REQUIRES_DIRECTIVE_NAME_IN_SPEC + ), + }.into() + }) + } + + pub(crate) fn requires_directive( + &self, + schema: &FederationSchema, + fields: NodeStr, + ) -> Result { + let name_in_schema = self + .directive_name_in_schema(schema, &FEDERATION_REQUIRES_DIRECTIVE_NAME_IN_SPEC)? + .ok_or_else(|| SingleFederationError::Internal { + message: "Unexpectedly could not find federation spec in schema".to_owned(), + })?; + Ok(Directive { + name: name_in_schema, + arguments: vec![Node::new(Argument { + name: FEDERATION_FIELDS_ARGUMENT_NAME, + value: Node::new(Value::String(fields)), + })], + }) + } + + pub(crate) fn requires_directive_arguments( + &self, + application: &Node, + ) -> Result { + Ok(RequiresDirectiveArguments { + fields: directive_required_fieldset_argument( + application, + &FEDERATION_FIELDS_ARGUMENT_NAME, + )?, + }) + } + + pub(crate) fn provides_directive_definition<'schema>( + &self, + schema: &'schema FederationSchema, + ) -> Result<&'schema Node, FederationError> { + self.directive_definition(schema, &FEDERATION_PROVIDES_DIRECTIVE_NAME_IN_SPEC)? + .ok_or_else(|| { + SingleFederationError::Internal { + message: format!( + "Unexpectedly could not find federation spec's \"@{}\" directive definition", + FEDERATION_PROVIDES_DIRECTIVE_NAME_IN_SPEC + ), + }.into() + }) + } + + pub(crate) fn provides_directive( + &self, + schema: &FederationSchema, + fields: NodeStr, + ) -> Result { + let name_in_schema = self + .directive_name_in_schema(schema, &FEDERATION_PROVIDES_DIRECTIVE_NAME_IN_SPEC)? + .ok_or_else(|| SingleFederationError::Internal { + message: "Unexpectedly could not find federation spec in schema".to_owned(), + })?; + Ok(Directive { + name: name_in_schema, + arguments: vec![Node::new(Argument { + name: FEDERATION_FIELDS_ARGUMENT_NAME, + value: Node::new(Value::String(fields)), + })], + }) + } + + pub(crate) fn provides_directive_arguments( + &self, + application: &Node, + ) -> Result { + Ok(ProvidesDirectiveArguments { + fields: directive_required_fieldset_argument( + application, + &FEDERATION_FIELDS_ARGUMENT_NAME, + )?, + }) + } + + pub(crate) fn shareable_directive( + &self, + schema: &FederationSchema, + ) -> Result { + let name_in_schema = self + .directive_name_in_schema(schema, &FEDERATION_SHAREABLE_DIRECTIVE_NAME_IN_SPEC)? + .ok_or_else(|| SingleFederationError::Internal { + message: "Unexpectedly could not find federation spec in schema".to_owned(), + })?; + Ok(Directive { + name: name_in_schema, + arguments: Vec::new(), + }) + } + + pub(crate) fn override_directive( + &self, + schema: &FederationSchema, + from: NodeStr, + ) -> Result { + let name_in_schema = self + .directive_name_in_schema(schema, &FEDERATION_OVERRIDE_DIRECTIVE_NAME_IN_SPEC)? + .ok_or_else(|| SingleFederationError::Internal { + message: "Unexpectedly could not find federation spec in schema".to_owned(), + })?; + Ok(Directive { + name: name_in_schema, + arguments: vec![Node::new(Argument { + name: FEDERATION_FROM_ARGUMENT_NAME, + value: Node::new(Value::String(from)), + })], + }) + } +} + +impl SpecDefinition for FederationSpecDefinition { + fn url(&self) -> &Url { + &self.url + } + + fn minimum_federation_version(&self) -> Option<&Version> { + None + } +} + +lazy_static! { + pub(crate) static ref FEDERATION_VERSIONS: SpecDefinitions = { + let mut definitions = SpecDefinitions::new(Identity::federation_identity()); + definitions.add(FederationSpecDefinition::new(Version { + major: 2, + minor: 0, + })); + definitions.add(FederationSpecDefinition::new(Version { + major: 2, + minor: 1, + })); + definitions.add(FederationSpecDefinition::new(Version { + major: 2, + minor: 2, + })); + definitions.add(FederationSpecDefinition::new(Version { + major: 2, + minor: 3, + })); + definitions.add(FederationSpecDefinition::new(Version { + major: 2, + minor: 4, + })); + definitions.add(FederationSpecDefinition::new(Version { + major: 2, + minor: 5, + })); + definitions + }; +} + +pub(crate) fn get_federation_spec_definition_from_subgraph( + schema: &FederationSchema, +) -> Result<&'static FederationSpecDefinition, FederationError> { + let federation_link = schema + .metadata() + .as_ref() + .and_then(|metadata| metadata.for_identity(&Identity::federation_identity())) + .ok_or_else(|| SingleFederationError::Internal { + message: "Subgraph unexpectedly does not use federation spec".to_owned(), + })?; + Ok(FEDERATION_VERSIONS + .find(&federation_link.url.version) + .ok_or_else(|| SingleFederationError::Internal { + message: "Subgraph unexpectedly does not use a supported federation spec version" + .to_owned(), + })?) +} diff --git a/apollo-federation/src/link/graphql_definition.rs b/apollo-federation/src/link/graphql_definition.rs new file mode 100644 index 0000000000..056747ae52 --- /dev/null +++ b/apollo-federation/src/link/graphql_definition.rs @@ -0,0 +1,109 @@ +use std::fmt::Display; + +use apollo_compiler::ast::Value; +use apollo_compiler::executable::Directive; +use apollo_compiler::executable::Name; +use apollo_compiler::name; +use apollo_compiler::Node; +use apollo_compiler::NodeStr; + +use crate::error::FederationError; +use crate::link::argument::directive_optional_string_argument; +use crate::link::argument::directive_optional_variable_boolean_argument; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub(crate) struct DeferDirectiveArguments { + label: Option, + if_: Option, +} + +impl DeferDirectiveArguments { + pub(crate) fn label(&self) -> Option<&NodeStr> { + self.label.as_ref() + } +} + +pub(crate) fn defer_directive_arguments( + application: &Node, +) -> Result { + Ok(DeferDirectiveArguments { + label: directive_optional_string_argument(application, &name!("label"))?, + if_: directive_optional_variable_boolean_argument(application, &name!("if"))?, + }) +} + +/// This struct is meant for recording the original structure/intent of `@skip`/`@include` +/// applications within the elements of a `GraphPath`. Accordingly, the order of them matters within +/// a `Vec`, and superfluous struct instances aren't elided; `Conditions` is the more appropriate +/// struct when trying to evaluate `@skip`/`@include` conditions (e.g. merging and short-circuiting +/// logic). +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub(crate) struct OperationConditional { + pub(crate) kind: OperationConditionalKind, + pub(crate) value: BooleanOrVariable, +} + +#[derive( + Debug, + Clone, + PartialEq, + Eq, + Hash, + strum_macros::Display, + strum_macros::EnumIter, + strum_macros::IntoStaticStr, +)] +pub(crate) enum OperationConditionalKind { + #[strum(to_string = "include")] + Include, + #[strum(to_string = "skip")] + Skip, +} + +impl OperationConditionalKind { + pub(crate) fn name(&self) -> Name { + match self { + OperationConditionalKind::Include => name!("include"), + OperationConditionalKind::Skip => name!("skip"), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub(crate) enum BooleanOrVariable { + Boolean(bool), + Variable(Name), +} + +impl Display for BooleanOrVariable { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + BooleanOrVariable::Boolean(b) => b.fmt(f), + BooleanOrVariable::Variable(name) => name.fmt(f), + } + } +} + +impl BooleanOrVariable { + pub(crate) fn to_ast_value(&self) -> Value { + match self { + BooleanOrVariable::Boolean(b) => Value::Boolean(*b), + BooleanOrVariable::Variable(name) => Value::Variable(name.clone()), + } + } +} + +impl From for Value { + fn from(b: BooleanOrVariable) -> Self { + match b { + BooleanOrVariable::Boolean(b) => Value::Boolean(b), + BooleanOrVariable::Variable(name) => Value::Variable(name), + } + } +} + +impl From for Node { + fn from(b: BooleanOrVariable) -> Self { + Node::new(b.into()) + } +} diff --git a/apollo-federation/src/link/inaccessible_spec_definition.rs b/apollo-federation/src/link/inaccessible_spec_definition.rs new file mode 100644 index 0000000000..6d3013facf --- /dev/null +++ b/apollo-federation/src/link/inaccessible_spec_definition.rs @@ -0,0 +1,1156 @@ +use std::fmt; + +use apollo_compiler::name; +use apollo_compiler::schema::Component; +use apollo_compiler::schema::ComponentName; +use apollo_compiler::schema::Directive; +use apollo_compiler::schema::DirectiveDefinition; +use apollo_compiler::schema::DirectiveLocation; +use apollo_compiler::schema::ExtendedType; +use apollo_compiler::schema::FieldDefinition; +use apollo_compiler::schema::InputValueDefinition; +use apollo_compiler::schema::Name; +use apollo_compiler::schema::Value; +use apollo_compiler::Node; +use indexmap::IndexMap; +use indexmap::IndexSet; +use lazy_static::lazy_static; + +use crate::error::FederationError; +use crate::error::MultipleFederationErrors; +use crate::error::SingleFederationError; +use crate::link::spec::Identity; +use crate::link::spec::Url; +use crate::link::spec::Version; +use crate::link::spec_definition::SpecDefinition; +use crate::link::spec_definition::SpecDefinitions; +use crate::schema::position; +use crate::schema::position::DirectiveDefinitionPosition; +use crate::schema::position::EnumValueDefinitionPosition; +use crate::schema::position::InputObjectFieldDefinitionPosition; +use crate::schema::position::InterfaceFieldArgumentDefinitionPosition; +use crate::schema::position::InterfaceFieldDefinitionPosition; +use crate::schema::position::ObjectFieldArgumentDefinitionPosition; +use crate::schema::position::ObjectFieldDefinitionPosition; +use crate::schema::position::SchemaRootDefinitionKind; +use crate::schema::position::TypeDefinitionPosition; +use crate::schema::FederationSchema; + +pub(crate) const INACCESSIBLE_DIRECTIVE_NAME_IN_SPEC: Name = name!("inaccessible"); + +pub(crate) struct InaccessibleSpecDefinition { + url: Url, + minimum_federation_version: Option, +} + +impl InaccessibleSpecDefinition { + pub(crate) fn new(version: Version, minimum_federation_version: Option) -> Self { + Self { + url: Url { + identity: Identity::inaccessible_identity(), + version, + }, + minimum_federation_version, + } + } + + pub(crate) fn inaccessible_directive( + &self, + schema: &FederationSchema, + ) -> Result { + let name_in_schema = self + .directive_name_in_schema(schema, &INACCESSIBLE_DIRECTIVE_NAME_IN_SPEC)? + .ok_or_else(|| SingleFederationError::Internal { + message: "Unexpectedly could not find inaccessible spec in schema".to_owned(), + })?; + Ok(Directive { + name: name_in_schema, + arguments: Vec::new(), + }) + } + + /// Returns the `@inaccessible` spec used in the given schema, if any. + /// + /// # Errors + /// Returns an error if the schema specifies an `@inaccessible` spec version that is not + /// supported by this version of the apollo-federation crate. + pub fn get_from_schema( + schema: &FederationSchema, + ) -> Result, FederationError> { + let inaccessible_link = match schema + .metadata() + .as_ref() + .and_then(|metadata| metadata.for_identity(&Identity::inaccessible_identity())) + { + None => return Ok(None), + Some(link) => link, + }; + Ok(Some( + INACCESSIBLE_VERSIONS + .find(&inaccessible_link.url.version) + .ok_or_else(|| SingleFederationError::Internal { + message: format!("Cannot remove inaccessible elements: the schema uses unsupported inaccessible spec version {}", inaccessible_link.url.version), + })?, + )) + } + + pub fn validate_inaccessible(&self, schema: &FederationSchema) -> Result<(), FederationError> { + validate_inaccessible(schema, self) + } + + pub fn remove_inaccessible_elements( + &self, + schema: &mut FederationSchema, + ) -> Result<(), FederationError> { + remove_inaccessible_elements(schema, self) + } +} + +impl SpecDefinition for InaccessibleSpecDefinition { + fn url(&self) -> &Url { + &self.url + } + + fn minimum_federation_version(&self) -> Option<&Version> { + self.minimum_federation_version.as_ref() + } +} + +lazy_static! { + pub(crate) static ref INACCESSIBLE_VERSIONS: SpecDefinitions = { + let mut definitions = SpecDefinitions::new(Identity::inaccessible_identity()); + definitions.add(InaccessibleSpecDefinition::new( + Version { major: 0, minor: 1 }, + None, + )); + definitions.add(InaccessibleSpecDefinition::new( + Version { major: 0, minor: 2 }, + Some(Version { major: 2, minor: 0 }), + )); + definitions + }; +} + +fn is_type_system_location(location: DirectiveLocation) -> bool { + matches!( + location, + DirectiveLocation::Schema + | DirectiveLocation::Scalar + | DirectiveLocation::Object + | DirectiveLocation::FieldDefinition + | DirectiveLocation::ArgumentDefinition + | DirectiveLocation::Interface + | DirectiveLocation::Union + | DirectiveLocation::Enum + | DirectiveLocation::EnumValue + | DirectiveLocation::InputObject + | DirectiveLocation::InputFieldDefinition + ) +} + +fn field_uses_inaccessible(field: &FieldDefinition, inaccessible_directive: &Name) -> bool { + field.directives.has(inaccessible_directive) + || field + .arguments + .iter() + .any(|argument| argument.directives.has(inaccessible_directive)) +} + +/// Check if a type definition uses the @inaccessible directive anywhere in its definition. +fn type_uses_inaccessible( + schema: &FederationSchema, + inaccessible_directive: &Name, + position: &TypeDefinitionPosition, +) -> Result { + Ok(match position { + TypeDefinitionPosition::Scalar(scalar_position) => { + let scalar = scalar_position.get(schema.schema())?; + scalar.directives.has(inaccessible_directive) + } + TypeDefinitionPosition::Object(object_position) => { + let object = object_position.get(schema.schema())?; + object.directives.has(inaccessible_directive) + || object + .fields + .values() + .any(|field| field_uses_inaccessible(field, inaccessible_directive)) + } + TypeDefinitionPosition::Interface(interface_position) => { + let interface = interface_position.get(schema.schema())?; + interface.directives.has(inaccessible_directive) + || interface + .fields + .values() + .any(|field| field_uses_inaccessible(field, inaccessible_directive)) + } + TypeDefinitionPosition::Union(union_position) => { + let union_ = union_position.get(schema.schema())?; + union_.directives.has(inaccessible_directive) + } + TypeDefinitionPosition::Enum(enum_position) => { + let enum_ = enum_position.get(schema.schema())?; + enum_.directives.has(inaccessible_directive) + || enum_ + .values + .values() + .any(|value| value.directives.has(inaccessible_directive)) + } + TypeDefinitionPosition::InputObject(input_object_position) => { + let input_object = input_object_position.get(schema.schema())?; + input_object.directives.has(inaccessible_directive) + || input_object + .fields + .values() + .any(|field| field.directives.has(inaccessible_directive)) + } + }) +} + +/// Check if a directive uses the @inaccessible directive anywhere in its definition. +fn directive_uses_inaccessible( + inaccessible_directive: &Name, + directive: &DirectiveDefinition, +) -> bool { + directive + .arguments + .iter() + .any(|argument| argument.directives.has(inaccessible_directive)) +} + +enum HasArgumentDefinitionsPosition { + ObjectField(ObjectFieldDefinitionPosition), + InterfaceField(InterfaceFieldDefinitionPosition), + DirectiveDefinition(DirectiveDefinitionPosition), +} +impl fmt::Display for HasArgumentDefinitionsPosition { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::ObjectField(x) => x.fmt(f), + Self::InterfaceField(x) => x.fmt(f), + Self::DirectiveDefinition(x) => x.fmt(f), + } + } +} + +fn validate_inaccessible_in_default_value( + schema: &FederationSchema, + inaccessible_directive: &Name, + value_type: &ExtendedType, + default_value: &Value, + // TODO avoid eagerly stringifying this + value_position: String, + errors: &mut MultipleFederationErrors, +) -> Result<(), FederationError> { + match (default_value, value_type) { + // Input fields can be referenced by schema default values. When an + // input field is hidden (but its parent isn't), we check that the + // arguments/input fields with such default values aren't in the API + // schema. + (Value::Object(value), ExtendedType::InputObject(type_)) => { + for (field_name, child_value) in value { + let Some(field) = type_.fields.get(field_name) else { + return Ok(()); + }; + let input_field_position = InputObjectFieldDefinitionPosition { + type_name: type_.name.clone(), + field_name: field_name.clone(), + }; + if input_field_position.is_inaccessible(schema, inaccessible_directive)? { + errors.push(SingleFederationError::DefaultValueUsesInaccessible { + message: format!("Input field `{input_field_position}` is @inaccessible but is used in the default value of `{value_position}`, which is in the API schema."), + }.into()); + } + + if let Some(field_type) = schema.schema().types.get(field.ty.inner_named_type()) { + validate_inaccessible_in_default_value( + schema, + inaccessible_directive, + field_type, + child_value, + value_position.clone(), + errors, + )?; + } + } + } + (Value::List(list), _) => { + for child_value in list { + validate_inaccessible_in_default_value( + schema, + inaccessible_directive, + value_type, + child_value, + value_position.clone(), + errors, + )?; + } + } + // Enum values can be referenced by schema default values. When an + // enum value is hidden (but its parent isn't), we check that the + // arguments/input fields with such default values aren't in the API + // schema. + // + // For back-compat, this also supports using string literals where an enum value is + // expected. + (Value::Enum(_) | Value::String(_), ExtendedType::Enum(type_)) => { + let value = match default_value { + Value::Enum(name) => name.clone(), + // It's no problem if this name is invalid. + Value::String(node_str) => Name::new_unchecked(node_str.clone()), + // Guaranteed to be enum or string by parent match branch. + _ => unreachable!(), + }; + let Some(enum_value) = type_.values.get(&value) else { + return Ok(()); + }; + let enum_value_position = EnumValueDefinitionPosition { + type_name: type_.name.clone(), + value_name: enum_value.value.clone(), + }; + if enum_value_position.is_inaccessible(schema, inaccessible_directive)? { + errors.push(SingleFederationError::DefaultValueUsesInaccessible { + message: format!("Enum value `{enum_value_position}` is @inaccessible but is used in the default value of `{value_position}`, which is in the API schema."), + }.into()); + } + } + _ => {} + } + Ok(()) +} + +fn validate_inaccessible_in_arguments( + schema: &FederationSchema, + inaccessible_directive: &Name, + usage_position: HasArgumentDefinitionsPosition, + arguments: &Vec>, + errors: &mut MultipleFederationErrors, +) -> Result<(), FederationError> { + let types = &schema.schema().types; + for arg in arguments { + let arg_name = &arg.name; + let arg_inaccessible = arg.directives.has(inaccessible_directive); + + // When an argument is hidden (but its parent isn't), we check that it + // isn't a required argument. + if arg_inaccessible && arg.is_required() { + let kind = match usage_position { + HasArgumentDefinitionsPosition::DirectiveDefinition(_) => "directive", + _ => "field", + }; + errors.push(SingleFederationError::RequiredInaccessible { + message: format!("Argument `{usage_position}({arg_name}:)` is @inaccessible but is a required argument of its {kind}."), + }.into()); + } + + if !arg_inaccessible { + if let (Some(default_value), Some(arg_type)) = + (&arg.default_value, types.get(arg.ty.inner_named_type())) + { + validate_inaccessible_in_default_value( + schema, + inaccessible_directive, + arg_type, + default_value, + format!("{usage_position}({arg_name}:)"), + errors, + )?; + } + } + } + Ok(()) +} + +fn validate_inaccessible_in_fields( + schema: &FederationSchema, + inaccessible_directive: &Name, + type_position: &TypeDefinitionPosition, + fields: &IndexMap>, + implements: &IndexSet, + errors: &mut MultipleFederationErrors, +) -> Result<(), FederationError> { + let mut has_inaccessible_field = false; + let mut has_accessible_field = false; + for (field_name, field) in fields { + let field_inaccessible = field.directives.has(inaccessible_directive); + has_inaccessible_field |= field_inaccessible; + has_accessible_field |= !field_inaccessible; + if field_inaccessible { + // Fields can be "referenced" by the corresponding fields of any + // interfaces their parent type implements. When a field is hidden + // (but its parent isn't), we check that such implemented fields + // aren't in the API schema. + let accessible_super_references = implements.iter().filter_map(|interface_name| { + let super_type = schema.schema().get_interface(interface_name)?; + if super_type.directives.has(inaccessible_directive) { + return None; + } + let super_field = super_type.fields.get(field_name)?; + if super_field.directives.has(inaccessible_directive) { + return None; + } + Some(InterfaceFieldDefinitionPosition { + type_name: super_type.name.clone(), + field_name: super_field.name.clone(), + }) + }); + + for super_position in accessible_super_references { + errors.push( + SingleFederationError::ImplementedByInaccessible { + message: format!("Field `{type_position}.{field_name}` is @inaccessible but implements the interface field `{super_position}`, which is in the API schema."), + } + .into(), + ); + } + } else { + // Arguments can be "referenced" by the corresponding arguments + // of any interfaces their parent type implements. When an + // argument is hidden (but its ancestors aren't), we check that + // such implemented arguments aren't in the API schema. + for arg in &field.arguments { + let arg_name = &arg.name; + let arg_inaccessible = arg.directives.has(inaccessible_directive); + + let accessible_super_references = implements.iter().filter_map(|interface_name| { + let super_type = schema.schema().get_interface(interface_name)?; + if super_type.directives.has(inaccessible_directive) { + return None; + } + let super_field = super_type.fields.get(field_name)?; + if super_field.directives.has(inaccessible_directive) { + return None; + } + let super_argument = super_field.argument_by_name(arg_name)?; + if super_argument.directives.has(inaccessible_directive) { + return None; + } + Some(InterfaceFieldArgumentDefinitionPosition { + type_name: super_type.name.clone(), + field_name: super_field.name.clone(), + argument_name: super_argument.name.clone(), + }) + }); + + if arg_inaccessible { + for accessible_reference in accessible_super_references { + errors.push(SingleFederationError::ImplementedByInaccessible { + message: format!("Argument `{type_position}.{field_name}({arg_name}:)` is @inaccessible but implements the interface argument `{accessible_reference}` which is in the API schema."), + }.into()); + } + } else if arg.is_required() { + // When an argument is accessible and required, we check that + // it isn't marked inaccessible in any interface implemented by + // the argument's field. This is because the GraphQL spec + // requires that any arguments of an implementing field that + // aren't in its implemented field are optional. + // + // You might be thinking that a required argument in an + // implementing field would necessitate that the implemented + // field would also require that argument (and thus the check + // in `validate_inaccessible_in_arguments` would also always + // error, removing the need for this one), but the GraphQL spec + // does not enforce this. E.g. it's valid GraphQL for the + // implementing and implemented arguments to be both + // non-nullable, but for just the implemented argument to have + // a default value. Not providing a value for the argument when + // querying the implemented type succeeds GraphQL operation + // validation, but results in input coercion failure for the + // field at runtime. + let inaccessible_super_references = + implements.iter().filter_map(|interface_name| { + let super_type = schema.schema().get_interface(interface_name)?; + if super_type.directives.has(inaccessible_directive) { + return None; + } + let super_field = super_type.fields.get(field_name)?; + if super_field.directives.has(inaccessible_directive) { + return None; + } + let super_argument = super_field.argument_by_name(arg_name)?; + if !super_argument.directives.has(inaccessible_directive) { + return None; + } + Some(InterfaceFieldArgumentDefinitionPosition { + type_name: super_type.name.clone(), + field_name: super_field.name.clone(), + argument_name: super_argument.name.clone(), + }) + }); + + for inaccessible_reference in inaccessible_super_references { + errors.push(SingleFederationError::RequiredInaccessible { + message: format!("Argument `{inaccessible_reference}` is @inaccessible but is implemented by the argument `{type_position}.{field_name}({arg_name}:)` which is in the API schema."), + }.into()); + } + } + } + validate_inaccessible_in_arguments( + schema, + inaccessible_directive, + match type_position { + TypeDefinitionPosition::Object(object) => { + HasArgumentDefinitionsPosition::ObjectField( + object.field(field.name.clone()), + ) + } + TypeDefinitionPosition::Interface(interface) => { + HasArgumentDefinitionsPosition::InterfaceField( + interface.field(field.name.clone()), + ) + } + _ => unreachable!(), + }, + &field.arguments, + errors, + )?; + } + } + + if has_inaccessible_field && !has_accessible_field { + errors.push(SingleFederationError::OnlyInaccessibleChildren { + message: format!("Type `{type_position}` is in the API schema but all of its members are @inaccessible."), + }.into()); + } + + Ok(()) +} + +/// Generic way to check for @inaccessible directives on a position or its parents. +trait IsInaccessibleExt { + /// Does this element, or any of its parents, have an @inaccessible directive? + /// + /// May return Err if `self` is an element that does not exist in the schema. + fn is_inaccessible( + &self, + schema: &FederationSchema, + inaccessible_directive: &Name, + ) -> Result; +} +impl IsInaccessibleExt for position::ObjectTypeDefinitionPosition { + fn is_inaccessible( + &self, + schema: &FederationSchema, + inaccessible_directive: &Name, + ) -> Result { + let object = self.get(schema.schema())?; + Ok(object.directives.has(inaccessible_directive)) + } +} +impl IsInaccessibleExt for position::ObjectFieldDefinitionPosition { + fn is_inaccessible( + &self, + schema: &FederationSchema, + inaccessible_directive: &Name, + ) -> Result { + // NOTE It'd be more efficient to start at parent and look up the field directly from there. + let field = self.get(schema.schema())?; + Ok(field.directives.has(inaccessible_directive) + || self + .parent() + .is_inaccessible(schema, inaccessible_directive)?) + } +} +impl IsInaccessibleExt for position::ObjectFieldArgumentDefinitionPosition { + fn is_inaccessible( + &self, + schema: &FederationSchema, + inaccessible_directive: &Name, + ) -> Result { + // NOTE It'd be more efficient to start at parent and look up the field and argument directly from there. + let argument = self.get(schema.schema())?; + Ok(argument.directives.has(inaccessible_directive) + || self + .parent() + .is_inaccessible(schema, inaccessible_directive)?) + } +} +impl IsInaccessibleExt for position::InterfaceTypeDefinitionPosition { + fn is_inaccessible( + &self, + schema: &FederationSchema, + inaccessible_directive: &Name, + ) -> Result { + let interface = self.get(schema.schema())?; + Ok(interface.directives.has(inaccessible_directive)) + } +} +impl IsInaccessibleExt for position::InterfaceFieldDefinitionPosition { + fn is_inaccessible( + &self, + schema: &FederationSchema, + inaccessible_directive: &Name, + ) -> Result { + // NOTE It'd be more efficient to start at parent and look up the field directly from there. + let field = self.get(schema.schema())?; + Ok(field.directives.has(inaccessible_directive) + || self + .parent() + .is_inaccessible(schema, inaccessible_directive)?) + } +} +impl IsInaccessibleExt for position::InterfaceFieldArgumentDefinitionPosition { + fn is_inaccessible( + &self, + schema: &FederationSchema, + inaccessible_directive: &Name, + ) -> Result { + // NOTE It'd be more efficient to start at parent and look up the field and argument directly from there. + let argument = self.get(schema.schema())?; + Ok(argument.directives.has(inaccessible_directive) + || self + .parent() + .is_inaccessible(schema, inaccessible_directive)?) + } +} +impl IsInaccessibleExt for position::InputObjectTypeDefinitionPosition { + fn is_inaccessible( + &self, + schema: &FederationSchema, + inaccessible_directive: &Name, + ) -> Result { + let input_object = self.get(schema.schema())?; + Ok(input_object.directives.has(inaccessible_directive)) + } +} +impl IsInaccessibleExt for position::InputObjectFieldDefinitionPosition { + fn is_inaccessible( + &self, + schema: &FederationSchema, + inaccessible_directive: &Name, + ) -> Result { + // NOTE It'd be more efficient to start at parent and look up the field directly from there. + let field = self.get(schema.schema())?; + Ok(field.directives.has(inaccessible_directive) + || self + .parent() + .is_inaccessible(schema, inaccessible_directive)?) + } +} +impl IsInaccessibleExt for position::ScalarTypeDefinitionPosition { + fn is_inaccessible( + &self, + schema: &FederationSchema, + inaccessible_directive: &Name, + ) -> Result { + let scalar = self.get(schema.schema())?; + Ok(scalar.directives.has(inaccessible_directive)) + } +} +impl IsInaccessibleExt for position::UnionTypeDefinitionPosition { + fn is_inaccessible( + &self, + schema: &FederationSchema, + inaccessible_directive: &Name, + ) -> Result { + let union_ = self.get(schema.schema())?; + Ok(union_.directives.has(inaccessible_directive)) + } +} +impl IsInaccessibleExt for position::EnumTypeDefinitionPosition { + fn is_inaccessible( + &self, + schema: &FederationSchema, + inaccessible_directive: &Name, + ) -> Result { + let enum_ = self.get(schema.schema())?; + Ok(enum_.directives.has(inaccessible_directive)) + } +} +impl IsInaccessibleExt for position::EnumValueDefinitionPosition { + fn is_inaccessible( + &self, + schema: &FederationSchema, + inaccessible_directive: &Name, + ) -> Result { + // NOTE It'd be more efficient to start at parent and look up the value directly from there. + let value = self.get(schema.schema())?; + Ok(value.directives.has(inaccessible_directive) + || self + .parent() + .is_inaccessible(schema, inaccessible_directive)?) + } +} +impl IsInaccessibleExt for position::DirectiveArgumentDefinitionPosition { + fn is_inaccessible( + &self, + schema: &FederationSchema, + inaccessible_directive: &Name, + ) -> Result { + let argument = self.get(schema.schema())?; + Ok(argument.directives.has(inaccessible_directive)) + } +} + +/// Types can be referenced by other schema elements in a few ways: +/// 1. Fields, arguments, and input fields may have the type as their base +/// type. +/// 2. Union types may have the type as a member (for object types). +/// 3. Object and interface types may implement the type (for interface +/// types). +/// 4. Schemas may have the type as a root operation type (for object +/// types). +/// +/// When a type is hidden, the referencer must follow certain rules for the +/// schema to be valid. Respectively, these rules are: +/// 1. The field/argument/input field must not be in the API schema. +/// 2. The union type, if empty, must not be in the API schema. +/// 3. No rules are imposed in this case. +/// 4. The root operation type must not be the query type. +/// +/// This function validates rules 1 and 4. +fn validate_inaccessible_type( + schema: &FederationSchema, + inaccessible_directive: &Name, + position: &TypeDefinitionPosition, + errors: &mut MultipleFederationErrors, +) -> Result<(), FederationError> { + let referencers = schema.referencers(); + + macro_rules! check_inaccessible_reference { + ( $ty:expr, $ref:expr ) => { + if !$ref.is_inaccessible(schema, inaccessible_directive)? { + errors.push(SingleFederationError::ReferencedInaccessible { + message: format!("Type `{}` is @inaccessible but is referenced by `{}`, which is in the API schema.", $ty, $ref), + }.into()) + } + } + } + + macro_rules! check_inaccessible_referencers { + ( $ty:expr, $( $referencers:expr ),+ ) => { + $( + for ref_position in $referencers { + check_inaccessible_reference!(position, ref_position); + } + )+ + } + } + + macro_rules! missing_referencers_error { + ( $position:expr ) => { + SingleFederationError::Internal { + message: format!( + "Type \"{}\" is marked inaccessible but does its referencers were not populated", + $position, + ), + } + } + } + + match position { + TypeDefinitionPosition::Scalar(scalar_position) => { + let referencers = referencers + .scalar_types + .get(&scalar_position.type_name) + .ok_or_else(|| missing_referencers_error!(&scalar_position))?; + check_inaccessible_referencers!( + position, + &referencers.object_fields, + &referencers.interface_fields, + &referencers.input_object_fields, + &referencers.object_field_arguments, + &referencers.interface_field_arguments, + &referencers.directive_arguments + ); + } + TypeDefinitionPosition::Object(object_position) => { + let referencers = referencers + .object_types + .get(&object_position.type_name) + .ok_or_else(|| missing_referencers_error!(&object_position))?; + if referencers + .schema_roots + .iter() + .any(|root| root.root_kind == SchemaRootDefinitionKind::Query) + { + errors.push(SingleFederationError::QueryRootTypeInaccessible { + message: format!("Type `{position}` is @inaccessible but is the query root type, which must be in the API schema."), + }.into()); + } + check_inaccessible_referencers!( + position, + &referencers.object_fields, + &referencers.interface_fields + ); + } + TypeDefinitionPosition::Interface(interface_position) => { + let referencers = referencers + .interface_types + .get(&interface_position.type_name) + .ok_or_else(|| missing_referencers_error!(&interface_position))?; + check_inaccessible_referencers!( + position, + &referencers.object_fields, + &referencers.interface_fields + ); + } + TypeDefinitionPosition::Union(union_position) => { + let referencers = referencers + .union_types + .get(&union_position.type_name) + .ok_or_else(|| missing_referencers_error!(&union_position))?; + check_inaccessible_referencers!( + position, + &referencers.object_fields, + &referencers.interface_fields + ); + } + TypeDefinitionPosition::Enum(enum_position) => { + let referencers = referencers + .enum_types + .get(&enum_position.type_name) + .ok_or_else(|| missing_referencers_error!(&enum_position))?; + check_inaccessible_referencers!( + position, + &referencers.object_fields, + &referencers.interface_fields, + &referencers.input_object_fields, + &referencers.object_field_arguments, + &referencers.interface_field_arguments, + &referencers.directive_arguments + ); + } + TypeDefinitionPosition::InputObject(input_object_position) => { + let referencers = referencers + .input_object_types + .get(&input_object_position.type_name) + .ok_or_else(|| missing_referencers_error!(&input_object_position))?; + check_inaccessible_referencers!( + position, + &referencers.input_object_fields, + &referencers.object_field_arguments, + &referencers.interface_field_arguments, + &referencers.directive_arguments + ); + } + } + + Ok(()) +} + +fn validate_inaccessible( + schema: &FederationSchema, + inaccessible_spec: &InaccessibleSpecDefinition, +) -> Result<(), FederationError> { + let inaccessible_directive = inaccessible_spec + .directive_name_in_schema(schema, &INACCESSIBLE_DIRECTIVE_NAME_IN_SPEC)? + .ok_or_else(|| SingleFederationError::Internal { + message: "Unexpectedly could not find inaccessible spec in schema".to_owned(), + })?; + + let mut errors = MultipleFederationErrors { errors: vec![] }; + + // Guaranteed to exist, we would not be able to look up the `inaccessible_spec` without having + // metadata. + let metadata = schema.metadata().unwrap(); + + for position in schema.get_types() { + let ty = position.get(schema.schema())?; + + // Core feature directives (and their descendants) aren't allowed to be + // @inaccessible. + // The JavaScript implementation checks for @inaccessible on built-in types here, as well. + // We don't do that because redefinitions of built-in types are already rejected + // by apollo-rs validation. + if metadata.source_link_of_type(position.type_name()).is_some() { + if type_uses_inaccessible(schema, &inaccessible_directive, &position)? { + errors.push( + SingleFederationError::DisallowedInaccessible { + message: format!( + "Core feature type `{position}` cannot use @inaccessible." + ), + } + .into(), + ) + } + continue; + } + + let is_inaccessible = ty.directives().has(&inaccessible_directive); + if is_inaccessible { + validate_inaccessible_type(schema, &inaccessible_directive, &position, &mut errors)?; + } else { + // This type must be in the API schema. For types with children (all types except scalar), + // we check that at least one of the children is accessible. + match &position { + TypeDefinitionPosition::Object(object_position) => { + let object = object_position.get(schema.schema())?; + validate_inaccessible_in_fields( + schema, + &inaccessible_directive, + &position, + &object.fields, + &object.implements_interfaces, + &mut errors, + )?; + } + TypeDefinitionPosition::Interface(interface_position) => { + let interface = interface_position.get(schema.schema())?; + validate_inaccessible_in_fields( + schema, + &inaccessible_directive, + &position, + &interface.fields, + &interface.implements_interfaces, + &mut errors, + )?; + } + TypeDefinitionPosition::InputObject(input_object_position) => { + let input_object = input_object_position.get(schema.schema())?; + let mut has_inaccessible_field = false; + let mut has_accessible_field = false; + for field in input_object.fields.values() { + let field_inaccessible = field.directives.has(&inaccessible_directive); + has_inaccessible_field |= field_inaccessible; + has_accessible_field |= !field_inaccessible; + + if field_inaccessible && field.is_required() { + errors.push(SingleFederationError::RequiredInaccessible{ + message: format!("Input field `{position}` is @inaccessible but is a required input field of its type."), + }.into()); + } + + if !field_inaccessible { + if let (Some(default_value), Some(field_type)) = ( + &field.default_value, + schema.schema().types.get(field.ty.inner_named_type()), + ) { + validate_inaccessible_in_default_value( + schema, + &inaccessible_directive, + field_type, + default_value, + input_object_position.field(field.name.clone()).to_string(), + &mut errors, + )?; + } + } + } + + if has_inaccessible_field && !has_accessible_field { + errors.push(SingleFederationError::OnlyInaccessibleChildren { + message: format!("Type `{position}` is in the API schema but all of its input fields are @inaccessible."), + }.into()); + } + } + TypeDefinitionPosition::Union(union_position) => { + let union_ = union_position.get(schema.schema())?; + let types = &schema.schema().types; + let any_accessible_member = union_.members.iter().any(|member| { + !types + .get(&member.name) + .is_some_and(|ty| ty.directives().has(&inaccessible_directive)) + }); + + if !any_accessible_member { + errors.push(SingleFederationError::OnlyInaccessibleChildren { + message: format!("Type `{position}` is in the API schema but all of its members are @inaccessible."), + }.into()); + } + } + TypeDefinitionPosition::Enum(enum_position) => { + let enum_ = enum_position.get(schema.schema())?; + let mut has_inaccessible_value = false; + let mut has_accessible_value = false; + for value in enum_.values.values() { + let value_inaccessible = value.directives.has(&inaccessible_directive); + has_inaccessible_value |= value_inaccessible; + has_accessible_value |= !value_inaccessible; + } + + if has_inaccessible_value && !has_accessible_value { + errors.push(SingleFederationError::OnlyInaccessibleChildren { + message: format!("Type `{enum_position}` is in the API schema but all of its members are @inaccessible."), + }.into()); + } + } + _ => {} + } + } + } + + for position in schema.get_directive_definitions() { + let directive = position.get(schema.schema())?; + let is_feature_directive = metadata + .source_link_of_directive(&position.directive_name) + .is_some(); + + let mut type_system_locations = directive + .locations + .iter() + .filter(|location| is_type_system_location(**location)) + .peekable(); + + if is_feature_directive { + if directive_uses_inaccessible(&inaccessible_directive, directive) { + errors.push( + SingleFederationError::DisallowedInaccessible { + message: format!( + "Core feature directive `{position}` cannot use @inaccessible.", + ), + } + .into(), + ); + } + } else if type_system_locations.peek().is_some() { + // Directives that can appear on type-system locations (and their + // descendants) aren't allowed to be @inaccessible. + if directive_uses_inaccessible(&inaccessible_directive, directive) { + let type_system_locations = type_system_locations + .map(ToString::to_string) + .collect::>() + .join(", "); + errors.push(SingleFederationError::DisallowedInaccessible { + message: format!("Directive `{position}` cannot use @inaccessible because it may be applied to these type-system locations: {}", type_system_locations), + }.into()); + } + } else { + // At this point, we know the directive must be in the API schema. Descend + // into the directive's arguments. + validate_inaccessible_in_arguments( + schema, + &inaccessible_directive, + HasArgumentDefinitionsPosition::DirectiveDefinition(position), + &directive.arguments, + &mut errors, + )?; + } + } + + if !errors.errors.is_empty() { + return Err(errors.into()); + } + + Ok(()) +} + +fn remove_inaccessible_elements( + schema: &mut FederationSchema, + inaccessible_spec: &InaccessibleSpecDefinition, +) -> Result<(), FederationError> { + let directive_name = inaccessible_spec + .directive_name_in_schema(schema, &INACCESSIBLE_DIRECTIVE_NAME_IN_SPEC)? + .ok_or_else(|| SingleFederationError::Internal { + message: "Unexpectedly could not find inaccessible spec in schema".to_owned(), + })?; + + // Find all elements that use @inaccessible. Clone so there's no live borrow. + let inaccessible_referencers = schema.referencers().get_directive(&directive_name)?.clone(); + + // Remove fields and arguments from inaccessible types first. If any inaccessible type has a field + // that references another inaccessible type, it would prevent the other type from being + // removed. + // We need an intermediate allocation as `.remove()` requires mutable access to the schema and + // looking up fields requires immutable access. + // + // This is a lot more verbose than in the JS implementation, but the JS implementation relies on + // being able to set references to `undefined`, and the types all working out in the end once + // the removal is complete, which our Rust data structures don't support. + let mut inaccessible_children: Vec = vec![]; + for position in &inaccessible_referencers.object_types { + let object = position.get(schema.schema())?; + inaccessible_children.extend( + object + .fields + .keys() + .map(|field_name| position.field(field_name.clone())), + ); + } + let mut inaccessible_arguments: Vec = vec![]; + for position in inaccessible_children { + let field = position.get(schema.schema())?; + inaccessible_arguments.extend( + field + .arguments + .iter() + .map(|argument| position.argument(argument.name.clone())), + ); + + position.remove(schema)?; + } + for position in inaccessible_arguments { + position.remove(schema)?; + } + + let mut inaccessible_children: Vec = vec![]; + for position in &inaccessible_referencers.interface_types { + let object = position.get(schema.schema())?; + inaccessible_children.extend( + object + .fields + .keys() + .map(|field_name| position.field(field_name.clone())), + ); + } + let mut inaccessible_arguments: Vec = vec![]; + for position in inaccessible_children { + let field = position.get(schema.schema())?; + inaccessible_arguments.extend( + field + .arguments + .iter() + .map(|argument| position.argument(argument.name.clone())), + ); + + position.remove(schema)?; + } + for position in inaccessible_arguments { + position.remove(schema)?; + } + + let mut inaccessible_children: Vec = vec![]; + for position in &inaccessible_referencers.input_object_types { + let object = position.get(schema.schema())?; + inaccessible_children.extend( + object + .fields + .keys() + .map(|field_name| position.field(field_name.clone())), + ); + } + for position in inaccessible_children { + position.remove(schema)?; + } + + for argument in inaccessible_referencers.directive_arguments { + argument.remove(schema)?; + } + for argument in inaccessible_referencers.interface_field_arguments { + argument.remove(schema)?; + } + for argument in inaccessible_referencers.object_field_arguments { + argument.remove(schema)?; + } + for field in inaccessible_referencers.input_object_fields { + field.remove(schema)?; + } + for field in inaccessible_referencers.interface_fields { + field.remove(schema)?; + } + for field in inaccessible_referencers.object_fields { + field.remove(schema)?; + } + for ty in inaccessible_referencers.union_types { + ty.remove(schema)?; + } + for ty in inaccessible_referencers.object_types { + ty.remove(schema)?; + } + for ty in inaccessible_referencers.interface_types { + ty.remove(schema)?; + } + for ty in inaccessible_referencers.input_object_types { + ty.remove(schema)?; + } + for value in inaccessible_referencers.enum_values { + value.remove(schema)?; + } + for ty in inaccessible_referencers.enum_types { + ty.remove(schema)?; + } + for ty in inaccessible_referencers.scalar_types { + ty.remove(schema)?; + } + + Ok(()) +} diff --git a/apollo-federation/src/link/join_spec_definition.rs b/apollo-federation/src/link/join_spec_definition.rs new file mode 100644 index 0000000000..c2b011e244 --- /dev/null +++ b/apollo-federation/src/link/join_spec_definition.rs @@ -0,0 +1,352 @@ +use apollo_compiler::name; +use apollo_compiler::schema::Directive; +use apollo_compiler::schema::DirectiveDefinition; +use apollo_compiler::schema::EnumType; +use apollo_compiler::schema::ExtendedType; +use apollo_compiler::schema::Name; +use apollo_compiler::Node; +use apollo_compiler::NodeStr; +use lazy_static::lazy_static; + +use crate::error::FederationError; +use crate::error::SingleFederationError; +use crate::link::argument::directive_optional_boolean_argument; +use crate::link::argument::directive_optional_enum_argument; +use crate::link::argument::directive_optional_fieldset_argument; +use crate::link::argument::directive_optional_string_argument; +use crate::link::argument::directive_required_enum_argument; +use crate::link::argument::directive_required_string_argument; +use crate::link::spec::Identity; +use crate::link::spec::Url; +use crate::link::spec::Version; +use crate::link::spec_definition::SpecDefinition; +use crate::link::spec_definition::SpecDefinitions; +use crate::schema::FederationSchema; + +pub(crate) const JOIN_GRAPH_ENUM_NAME_IN_SPEC: Name = name!("Graph"); +pub(crate) const JOIN_GRAPH_DIRECTIVE_NAME_IN_SPEC: Name = name!("graph"); +pub(crate) const JOIN_TYPE_DIRECTIVE_NAME_IN_SPEC: Name = name!("type"); +pub(crate) const JOIN_FIELD_DIRECTIVE_NAME_IN_SPEC: Name = name!("field"); +pub(crate) const JOIN_IMPLEMENTS_DIRECTIVE_NAME_IN_SPEC: Name = name!("implements"); +pub(crate) const JOIN_UNIONMEMBER_DIRECTIVE_NAME_IN_SPEC: Name = name!("unionMember"); +pub(crate) const JOIN_ENUMVALUE_DIRECTIVE_NAME_IN_SPEC: Name = name!("enumValue"); + +pub(crate) const JOIN_NAME_ARGUMENT_NAME: Name = name!("name"); +pub(crate) const JOIN_URL_ARGUMENT_NAME: Name = name!("url"); +pub(crate) const JOIN_GRAPH_ARGUMENT_NAME: Name = name!("graph"); +pub(crate) const JOIN_KEY_ARGUMENT_NAME: Name = name!("key"); +pub(crate) const JOIN_EXTENSION_ARGUMENT_NAME: Name = name!("extension"); +pub(crate) const JOIN_RESOLVABLE_ARGUMENT_NAME: Name = name!("resolvable"); +pub(crate) const JOIN_ISINTERFACEOBJECT_ARGUMENT_NAME: Name = name!("isInterfaceObject"); +pub(crate) const JOIN_REQUIRES_ARGUMENT_NAME: Name = name!("requires"); +pub(crate) const JOIN_PROVIDES_ARGUMENT_NAME: Name = name!("provides"); +pub(crate) const JOIN_TYPE_ARGUMENT_NAME: Name = name!("type"); +pub(crate) const JOIN_EXTERNAL_ARGUMENT_NAME: Name = name!("external"); +pub(crate) const JOIN_OVERRIDE_ARGUMENT_NAME: Name = name!("override"); +pub(crate) const JOIN_USEROVERRIDDEN_ARGUMENT_NAME: Name = name!("usedOverridden"); +pub(crate) const JOIN_INTERFACE_ARGUMENT_NAME: Name = name!("interface"); +pub(crate) const JOIN_MEMBER_ARGUMENT_NAME: Name = name!("member"); + +pub(crate) struct GraphDirectiveArguments { + pub(crate) name: NodeStr, + pub(crate) url: NodeStr, +} + +pub(crate) struct TypeDirectiveArguments { + pub(crate) graph: Name, + pub(crate) key: Option, + pub(crate) extension: bool, + pub(crate) resolvable: bool, + pub(crate) is_interface_object: bool, +} + +pub(crate) struct FieldDirectiveArguments { + pub(crate) graph: Option, + pub(crate) requires: Option, + pub(crate) provides: Option, + pub(crate) type_: Option, + pub(crate) external: Option, + pub(crate) override_: Option, + pub(crate) user_overridden: Option, +} + +pub(crate) struct ImplementsDirectiveArguments { + pub(crate) graph: Name, + pub(crate) interface: NodeStr, +} + +pub(crate) struct UnionMemberDirectiveArguments { + pub(crate) graph: Name, + pub(crate) member: NodeStr, +} + +pub(crate) struct EnumValueDirectiveArguments { + pub(crate) graph: Name, +} + +#[derive(Clone)] +pub(crate) struct JoinSpecDefinition { + url: Url, + minimum_federation_version: Option, +} + +impl JoinSpecDefinition { + pub(crate) fn new(version: Version, minimum_federation_version: Option) -> Self { + Self { + url: Url { + identity: Identity::join_identity(), + version, + }, + minimum_federation_version, + } + } + + pub(crate) fn graph_enum_definition<'schema>( + &self, + schema: &'schema FederationSchema, + ) -> Result<&'schema Node, FederationError> { + let type_ = self + .type_definition(schema, &JOIN_GRAPH_ENUM_NAME_IN_SPEC)? + .ok_or_else(|| SingleFederationError::Internal { + message: "Unexpectedly could not find join spec in schema".to_owned(), + })?; + if let ExtendedType::Enum(ref type_) = type_ { + Ok(type_) + } else { + Err(SingleFederationError::Internal { + message: format!( + "Unexpectedly found non-enum for join spec's \"{}\" enum definition", + JOIN_GRAPH_ENUM_NAME_IN_SPEC, + ), + } + .into()) + } + } + + pub(crate) fn graph_directive_definition<'schema>( + &self, + schema: &'schema FederationSchema, + ) -> Result<&'schema Node, FederationError> { + self.directive_definition(schema, &JOIN_GRAPH_DIRECTIVE_NAME_IN_SPEC)? + .ok_or_else(|| { + SingleFederationError::Internal { + message: "Unexpectedly could not find join spec in schema".to_owned(), + } + .into() + }) + } + + pub(crate) fn graph_directive_arguments( + &self, + application: &Node, + ) -> Result { + Ok(GraphDirectiveArguments { + name: directive_required_string_argument(application, &JOIN_NAME_ARGUMENT_NAME)?, + url: directive_required_string_argument(application, &JOIN_URL_ARGUMENT_NAME)?, + }) + } + + pub(crate) fn type_directive_definition<'schema>( + &self, + schema: &'schema FederationSchema, + ) -> Result<&'schema Node, FederationError> { + self.directive_definition(schema, &JOIN_TYPE_DIRECTIVE_NAME_IN_SPEC)? + .ok_or_else(|| { + SingleFederationError::Internal { + message: "Unexpectedly could not find join spec in schema".to_owned(), + } + .into() + }) + } + + pub(crate) fn type_directive_arguments( + &self, + application: &Node, + ) -> Result { + Ok(TypeDirectiveArguments { + graph: directive_required_enum_argument(application, &JOIN_GRAPH_ARGUMENT_NAME)?, + key: directive_optional_fieldset_argument(application, &JOIN_KEY_ARGUMENT_NAME)?, + extension: directive_optional_boolean_argument( + application, + &JOIN_EXTENSION_ARGUMENT_NAME, + )? + .unwrap_or(false), + resolvable: directive_optional_boolean_argument( + application, + &JOIN_RESOLVABLE_ARGUMENT_NAME, + )? + .unwrap_or(true), + is_interface_object: directive_optional_boolean_argument( + application, + &JOIN_ISINTERFACEOBJECT_ARGUMENT_NAME, + )? + .unwrap_or(false), + }) + } + + pub(crate) fn field_directive_definition<'schema>( + &self, + schema: &'schema FederationSchema, + ) -> Result<&'schema Node, FederationError> { + self.directive_definition(schema, &JOIN_FIELD_DIRECTIVE_NAME_IN_SPEC)? + .ok_or_else(|| { + SingleFederationError::Internal { + message: "Unexpectedly could not find join spec in schema".to_owned(), + } + .into() + }) + } + + pub(crate) fn field_directive_arguments( + &self, + application: &Node, + ) -> Result { + Ok(FieldDirectiveArguments { + graph: directive_optional_enum_argument(application, &JOIN_GRAPH_ARGUMENT_NAME)?, + requires: directive_optional_fieldset_argument( + application, + &JOIN_REQUIRES_ARGUMENT_NAME, + )?, + provides: directive_optional_fieldset_argument( + application, + &JOIN_PROVIDES_ARGUMENT_NAME, + )?, + type_: directive_optional_string_argument(application, &JOIN_TYPE_ARGUMENT_NAME)?, + external: directive_optional_boolean_argument( + application, + &JOIN_EXTERNAL_ARGUMENT_NAME, + )?, + override_: directive_optional_string_argument( + application, + &JOIN_OVERRIDE_ARGUMENT_NAME, + )?, + user_overridden: directive_optional_boolean_argument( + application, + &JOIN_USEROVERRIDDEN_ARGUMENT_NAME, + )?, + }) + } + + pub(crate) fn implements_directive_definition<'schema>( + &self, + schema: &'schema FederationSchema, + ) -> Result>, FederationError> { + if *self.version() < (Version { major: 0, minor: 2 }) { + return Ok(None); + } + self.directive_definition(schema, &JOIN_IMPLEMENTS_DIRECTIVE_NAME_IN_SPEC)? + .ok_or_else(|| { + SingleFederationError::Internal { + message: "Unexpectedly could not find join spec in schema".to_owned(), + } + .into() + }) + .map(Some) + } + + pub(crate) fn implements_directive_arguments( + &self, + application: &Node, + ) -> Result { + Ok(ImplementsDirectiveArguments { + graph: directive_required_enum_argument(application, &JOIN_GRAPH_ARGUMENT_NAME)?, + interface: directive_required_string_argument( + application, + &JOIN_INTERFACE_ARGUMENT_NAME, + )?, + }) + } + + pub(crate) fn union_member_directive_definition<'schema>( + &self, + schema: &'schema FederationSchema, + ) -> Result>, FederationError> { + if *self.version() < (Version { major: 0, minor: 3 }) { + return Ok(None); + } + self.directive_definition(schema, &JOIN_UNIONMEMBER_DIRECTIVE_NAME_IN_SPEC)? + .ok_or_else(|| { + SingleFederationError::Internal { + message: "Unexpectedly could not find join spec in schema".to_owned(), + } + .into() + }) + .map(Some) + } + + pub(crate) fn union_member_directive_arguments( + &self, + application: &Node, + ) -> Result { + Ok(UnionMemberDirectiveArguments { + graph: directive_required_enum_argument(application, &JOIN_GRAPH_ARGUMENT_NAME)?, + member: directive_required_string_argument(application, &JOIN_MEMBER_ARGUMENT_NAME)?, + }) + } + + pub(crate) fn enum_value_directive_definition<'schema>( + &self, + schema: &'schema FederationSchema, + ) -> Result>, FederationError> { + if *self.version() < (Version { major: 0, minor: 3 }) { + return Ok(None); + } + self.directive_definition(schema, &JOIN_ENUMVALUE_DIRECTIVE_NAME_IN_SPEC)? + .ok_or_else(|| { + SingleFederationError::Internal { + message: "Unexpectedly could not find join spec in schema".to_owned(), + } + .into() + }) + .map(Some) + } + + pub(crate) fn enum_value_directive_arguments( + &self, + application: &Node, + ) -> Result { + Ok(EnumValueDirectiveArguments { + graph: directive_required_enum_argument(application, &JOIN_GRAPH_ARGUMENT_NAME)?, + }) + } +} + +impl SpecDefinition for JoinSpecDefinition { + fn url(&self) -> &Url { + &self.url + } + + fn minimum_federation_version(&self) -> Option<&Version> { + self.minimum_federation_version.as_ref() + } +} + +lazy_static! { + pub(crate) static ref JOIN_VERSIONS: SpecDefinitions = { + let mut definitions = SpecDefinitions::new(Identity::join_identity()); + definitions.add(JoinSpecDefinition::new( + Version { major: 0, minor: 1 }, + None, + )); + definitions.add(JoinSpecDefinition::new( + Version { major: 0, minor: 2 }, + None, + )); + definitions.add(JoinSpecDefinition::new( + Version { major: 0, minor: 3 }, + Some(Version { major: 2, minor: 0 }), + )); + definitions + }; + + /// Versions supported for purpose other than query planning. + /// TODO: remove once v0.4 is properly implemented in query planning + pub(crate) static ref NON_QUERY_PLANNING_JOIN_VERSIONS: SpecDefinitions = { + let mut definitions = JOIN_VERSIONS.clone(); + definitions.add(JoinSpecDefinition::new( + Version { major: 0, minor: 4 }, + Some(Version { major: 2, minor: 7 }), + )); + definitions + }; +} diff --git a/apollo-federation/src/link/link_spec_definition.rs b/apollo-federation/src/link/link_spec_definition.rs new file mode 100644 index 0000000000..f1ed4035f7 --- /dev/null +++ b/apollo-federation/src/link/link_spec_definition.rs @@ -0,0 +1,61 @@ +use lazy_static::lazy_static; + +use crate::link::spec::Identity; +use crate::link::spec::Url; +use crate::link::spec::Version; +use crate::link::spec_definition::SpecDefinition; +use crate::link::spec_definition::SpecDefinitions; + +pub(crate) struct LinkSpecDefinition { + url: Url, + minimum_federation_version: Option, +} + +impl LinkSpecDefinition { + pub(crate) fn new( + version: Version, + minimum_federation_version: Option, + identity: Identity, + ) -> Self { + Self { + url: Url { identity, version }, + minimum_federation_version, + } + } +} + +impl SpecDefinition for LinkSpecDefinition { + fn url(&self) -> &Url { + &self.url + } + + fn minimum_federation_version(&self) -> Option<&Version> { + self.minimum_federation_version.as_ref() + } +} + +lazy_static! { + pub(crate) static ref CORE_VERSIONS: SpecDefinitions = { + let mut definitions = SpecDefinitions::new(Identity::core_identity()); + definitions.add(LinkSpecDefinition::new( + Version { major: 0, minor: 1 }, + None, + Identity::core_identity(), + )); + definitions.add(LinkSpecDefinition::new( + Version { major: 0, minor: 2 }, + Some(Version { major: 2, minor: 0 }), + Identity::core_identity(), + )); + definitions + }; + pub(crate) static ref LINK_VERSIONS: SpecDefinitions = { + let mut definitions = SpecDefinitions::new(Identity::link_identity()); + definitions.add(LinkSpecDefinition::new( + Version { major: 1, minor: 0 }, + Some(Version { major: 2, minor: 0 }), + Identity::link_identity(), + )); + definitions + }; +} diff --git a/apollo-federation/src/link/mod.rs b/apollo-federation/src/link/mod.rs new file mode 100644 index 0000000000..16d8379c05 --- /dev/null +++ b/apollo-federation/src/link/mod.rs @@ -0,0 +1,459 @@ +use std::collections::HashMap; +use std::fmt; +use std::str; +use std::sync::Arc; + +use apollo_compiler::ast::Directive; +use apollo_compiler::ast::InvalidNameError; +use apollo_compiler::ast::Value; +use apollo_compiler::name; +use apollo_compiler::schema::Name; +use apollo_compiler::Node; +use apollo_compiler::NodeStr; +use thiserror::Error; + +use crate::error::FederationError; +use crate::error::SingleFederationError; +use crate::link::link_spec_definition::LinkSpecDefinition; +use crate::link::link_spec_definition::CORE_VERSIONS; +use crate::link::link_spec_definition::LINK_VERSIONS; +use crate::link::spec::Identity; +use crate::link::spec::Url; + +pub(crate) mod argument; +pub mod database; +pub(crate) mod federation_spec_definition; +pub(crate) mod graphql_definition; +pub(crate) mod inaccessible_spec_definition; +pub(crate) mod join_spec_definition; +pub(crate) mod link_spec_definition; +pub mod spec; +pub(crate) mod spec_definition; + +pub const DEFAULT_LINK_NAME: Name = name!("link"); +pub const DEFAULT_IMPORT_SCALAR_NAME: Name = name!("Import"); +pub const DEFAULT_PURPOSE_ENUM_NAME: Name = name!("Purpose"); + +// TODO: we should provide proper "diagnostic" here, linking to ast, accumulating more than one +// error and whatnot. +#[derive(Error, Debug, PartialEq)] +pub enum LinkError { + #[error(transparent)] + InvalidName(#[from] InvalidNameError), + #[error("Invalid use of @link in schema: {0}")] + BootstrapError(String), +} + +// TODO: Replace LinkError usages with FederationError. +impl From for FederationError { + fn from(value: LinkError) -> Self { + SingleFederationError::InvalidLinkDirectiveUsage { + message: value.to_string(), + } + .into() + } +} + +#[derive(Eq, PartialEq, Debug)] +pub enum Purpose { + SECURITY, + EXECUTION, +} + +impl Purpose { + pub fn from_value(value: &Value) -> Result { + if let Value::Enum(value) = value { + Ok(value.parse::()?) + } else { + Err(LinkError::BootstrapError( + "invalid `purpose` value, should be an enum".to_string(), + )) + } + } +} + +impl str::FromStr for Purpose { + type Err = LinkError; + + fn from_str(s: &str) -> Result { + match s { + "SECURITY" => Ok(Purpose::SECURITY), + "EXECUTION" => Ok(Purpose::EXECUTION), + _ => Err(LinkError::BootstrapError(format!( + "invalid/unrecognized `purpose` value '{}'", + s + ))), + } + } +} + +impl fmt::Display for Purpose { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Purpose::SECURITY => f.write_str("SECURITY"), + Purpose::EXECUTION => f.write_str("EXECUTION"), + } + } +} + +impl From<&Purpose> for Name { + fn from(value: &Purpose) -> Self { + match value { + Purpose::SECURITY => name!("SECURITY"), + Purpose::EXECUTION => name!("EXECUTION"), + } + } +} + +#[derive(Eq, PartialEq, Debug)] +pub struct Import { + /// The name of the element that is being imported. + /// + /// Note that this will never start with '@': whether or not this is the name of a directive is + /// entirely reflected by the value of `is_directive`. + pub element: Name, + + /// Whether the imported element is a directive (if it is not, then it is an imported type). + pub is_directive: bool, + + /// The optional alias under which the element is imported. + pub alias: Option, +} + +impl Import { + pub fn from_value(value: &Value) -> Result { + // TODO: it could be nice to include the broken value in the error messages of this method + // (especially since @link(import:) is a list), but `Value` does not implement `Display` + // currently, so a bit annoying. + match value { + Value::String(str) => { + if let Some(directive_name) = str.strip_prefix('@') { + Ok(Import { element: Name::new(directive_name)?, is_directive: true, alias: None }) + } else { + Ok(Import { element: Name::new(str.clone())?, is_directive: false, alias: None }) + } + }, + Value::Object(fields) => { + let mut name: Option = None; + let mut alias: Option = None; + for (k, v) in fields { + match k.as_str() { + "name" => { + name = Some(v.as_node_str().ok_or_else(|| { + LinkError::BootstrapError("invalid value for `name` field in @link(import:) argument: must be a string".to_string()) + })?.clone()) + }, + "as" => { + alias = Some(v.as_node_str().ok_or_else(|| { + LinkError::BootstrapError("invalid value for `as` field in @link(import:) argument: must be a string".to_string()) + })?.clone()) + }, + _ => Err(LinkError::BootstrapError(format!("unknown field `{k}` in @link(import:) argument")))? + } + } + if let Some(element) = name { + if let Some(directive_name) = element.strip_prefix('@') { + if let Some(alias_str) = alias.as_ref() { + let Some(alias_str) = alias_str.strip_prefix('@') else { + return Err(LinkError::BootstrapError(format!("invalid alias '{}' for import name '{}': should start with '@' since the imported name does", alias_str, element))); + }; + alias = Some(alias_str.into()); + } + Ok(Import { + element: Name::new(directive_name)?, + is_directive: true, + alias: alias.map(Name::new).transpose()?, + }) + } else { + if let Some(alias) = &alias { + if alias.starts_with('@') { + return Err(LinkError::BootstrapError(format!("invalid alias '{}' for import name '{}': should not start with '@' (or, if {} is a directive, then the name should start with '@')", alias, element, element))); + } + } + Ok(Import { + element: Name::new(element)?, + is_directive: false, + alias: alias.map(Name::new).transpose()?, + }) + } + } else { + Err(LinkError::BootstrapError("invalid entry in @link(import:) argument, missing mandatory `name` field".to_string())) + } + }, + _ => Err(LinkError::BootstrapError("invalid sub-value for @link(import:) argument: values should be either strings or input object values of the form { name: \"\", as: \"\" }.".to_string())) + } + } + + pub fn element_display_name(&self) -> impl fmt::Display + '_ { + DisplayName { + name: &self.element, + is_directive: self.is_directive, + } + } + + pub fn imported_name(&self) -> &Name { + return self.alias.as_ref().unwrap_or(&self.element); + } + + pub fn imported_display_name(&self) -> impl fmt::Display + '_ { + DisplayName { + name: self.imported_name(), + is_directive: self.is_directive, + } + } +} + +/// A [`fmt::Display`]able wrapper for name strings that adds an `@` in front for directive names. +struct DisplayName<'s> { + name: &'s str, + is_directive: bool, +} + +impl<'s> fmt::Display for DisplayName<'s> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.is_directive { + f.write_str("@")?; + } + f.write_str(self.name) + } +} + +impl fmt::Display for Import { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.alias.is_some() { + write!( + f, + r#"{{ name: "{}", as: "{}" }}"#, + self.element_display_name(), + self.imported_display_name() + ) + } else { + write!(f, r#""{}""#, self.imported_display_name()) + } + } +} + +#[derive(Debug, Eq, PartialEq)] +pub struct Link { + pub url: Url, + pub spec_alias: Option, + pub imports: Vec>, + pub purpose: Option, +} + +impl Link { + pub fn spec_name_in_schema(&self) -> &Name { + self.spec_alias.as_ref().unwrap_or(&self.url.identity.name) + } + + pub fn directive_name_in_schema(&self, name: &Name) -> Name { + // If the directive is imported, then it's name in schema is whatever name it is + // imported under. Otherwise, it is usually fully qualified by the spec name (so, + // something like 'federation__key'), but there is a special case for directives + // whose name match the one of the spec: those don't get qualified. + if let Some(import) = self.imports.iter().find(|i| i.element == *name) { + import.alias.clone().unwrap_or_else(|| name.clone()) + } else if name == self.url.identity.name.as_str() { + self.spec_name_in_schema().clone() + } else { + // Both sides are `Name`s and we just add valid characters in between. + Name::new_unchecked(format!("{}__{}", self.spec_name_in_schema(), name).into()) + } + } + + pub fn type_name_in_schema(&self, name: &Name) -> Name { + // Similar to directives, but the special case of a directive name matching the spec + // name does not apply to types. + if let Some(import) = self.imports.iter().find(|i| i.element == *name) { + import.alias.clone().unwrap_or_else(|| name.clone()) + } else { + // Both sides are `Name`s and we just add valid characters in between. + Name::new_unchecked(format!("{}__{}", self.spec_name_in_schema(), name).into()) + } + } + + pub fn from_directive_application(directive: &Node) -> Result { + let (url, is_link) = if let Some(value) = directive.argument_by_name("url") { + (value, true) + } else if let Some(value) = directive.argument_by_name("feature") { + // XXX(@goto-bus-stop): @core compatibility is primarily to support old tests--should be + // removed when those are updated. + (value, false) + } else { + return Err(LinkError::BootstrapError( + "the `url` argument for @link is mandatory".to_string(), + )); + }; + + let (directive_name, arg_name) = if is_link { + ("link", "url") + } else { + ("core", "feature") + }; + + let url = url.as_str().ok_or_else(|| { + LinkError::BootstrapError(format!( + "the `{arg_name}` argument for @{directive_name} must be a String" + )) + })?; + let url: Url = url.parse::().map_err(|e| { + LinkError::BootstrapError(format!("invalid `{arg_name}` argument (reason: {e})")) + })?; + + let spec_alias = directive + .argument_by_name("as") + .and_then(|arg| arg.as_node_str()) + .map(Name::new) + .transpose()?; + let purpose = if let Some(value) = directive.argument_by_name("for") { + Some(Purpose::from_value(value)?) + } else { + None + }; + + let imports = if is_link { + directive + .argument_by_name("import") + .and_then(|arg| arg.as_list()) + .unwrap_or(&[]) + .iter() + .map(|value| Ok(Arc::new(Import::from_value(value)?))) + .collect::>, LinkError>>()? + } else { + Default::default() + }; + + Ok(Link { + url, + spec_alias, + imports, + purpose, + }) + } +} + +impl fmt::Display for Link { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let imported_types: Vec = self + .imports + .iter() + .map(|import| import.to_string()) + .collect::>(); + let imports = if imported_types.is_empty() { + "".to_string() + } else { + format!(r#", import: [{}]"#, imported_types.join(", ")) + }; + let alias = self + .spec_alias + .as_ref() + .map(|a| format!(r#", as: "{}""#, a)) + .unwrap_or("".to_string()); + let purpose = self + .purpose + .as_ref() + .map(|p| format!(r#", for: {}"#, p)) + .unwrap_or("".to_string()); + write!(f, r#"@link(url: "{}"{alias}{imports}{purpose})"#, self.url) + } +} + +#[derive(Debug)] +pub struct LinkedElement { + pub link: Arc, + pub import: Option>, +} + +#[derive(Default, Eq, PartialEq, Debug)] +pub struct LinksMetadata { + pub(crate) links: Vec>, + pub(crate) by_identity: HashMap>, + pub(crate) by_name_in_schema: HashMap>, + pub(crate) types_by_imported_name: HashMap, Arc)>, + pub(crate) directives_by_imported_name: HashMap, Arc)>, +} + +impl LinksMetadata { + pub(crate) fn link_spec_definition( + &self, + ) -> Result<&'static LinkSpecDefinition, FederationError> { + if let Some(link_link) = self.for_identity(&Identity::link_identity()) { + LINK_VERSIONS.find(&link_link.url.version).ok_or_else(|| { + SingleFederationError::Internal { + message: format!("Unexpected link spec version {}", link_link.url.version), + } + .into() + }) + } else if let Some(core_link) = self.for_identity(&Identity::core_identity()) { + CORE_VERSIONS.find(&core_link.url.version).ok_or_else(|| { + SingleFederationError::Internal { + message: format!("Unexpected core spec version {}", core_link.url.version), + } + .into() + }) + } else { + Err(SingleFederationError::Internal { + message: "Unexpectedly could not find core/link spec".to_owned(), + } + .into()) + } + } + + pub fn all_links(&self) -> &[Arc] { + return self.links.as_ref(); + } + + pub fn for_identity(&self, identity: &Identity) -> Option> { + return self.by_identity.get(identity).cloned(); + } + + pub fn source_link_of_type(&self, type_name: &Name) -> Option { + // For types, it's either an imported name or it must be fully qualified + + if let Some((link, import)) = self.types_by_imported_name.get(type_name) { + Some(LinkedElement { + link: Arc::clone(link), + import: Some(Arc::clone(import)), + }) + } else { + type_name.split_once("__").and_then(|(spec_name, _)| { + self.by_name_in_schema + .get(spec_name) + .map(|link| LinkedElement { + link: Arc::clone(link), + import: None, + }) + }) + } + } + + pub fn source_link_of_directive(&self, directive_name: &Name) -> Option { + // For directives, it can be either: + // 1. be an imported name, + // 2. be the "imported" name of a linked spec (special case of a directive named like the + // spec), + // 3. or it must be fully qualified. + if let Some((link, import)) = self.directives_by_imported_name.get(directive_name) { + return Some(LinkedElement { + link: Arc::clone(link), + import: Some(Arc::clone(import)), + }); + } + + if let Some(link) = self.by_name_in_schema.get(directive_name) { + return Some(LinkedElement { + link: Arc::clone(link), + import: None, + }); + } + + directive_name.split_once("__").and_then(|(spec_name, _)| { + self.by_name_in_schema + .get(spec_name) + .map(|link| LinkedElement { + link: Arc::clone(link), + import: None, + }) + }) + } +} diff --git a/apollo-federation/src/link/spec.rs b/apollo-federation/src/link/spec.rs new file mode 100644 index 0000000000..e5982a10dd --- /dev/null +++ b/apollo-federation/src/link/spec.rs @@ -0,0 +1,352 @@ +//! Representation of Apollo `@link` specifications. +use std::fmt; +use std::str; + +use apollo_compiler::ast::Name; +use apollo_compiler::name; +use thiserror::Error; + +use crate::error::FederationError; +use crate::error::SingleFederationError; + +pub const APOLLO_SPEC_DOMAIN: &str = "https://specs.apollo.dev"; + +#[derive(Error, Debug, PartialEq)] +pub enum SpecError { + #[error("Parse error: {0}")] + ParseError(String), +} + +// TODO: Replace SpecError usages with FederationError. +impl From for FederationError { + fn from(value: SpecError) -> Self { + SingleFederationError::InvalidLinkIdentifier { + message: value.to_string(), + } + .into() + } +} + +/// Represents the identity of a `@link` specification, which uniquely identify a specification. +#[derive(Clone, PartialEq, Eq, Hash, Debug)] +pub struct Identity { + /// The "domain" of which the specification this identifies is part of. + /// For instance, `"https://specs.apollo.dev"`. + pub domain: String, + + /// The name of the specification this identifies. + /// For instance, "federation". + pub name: Name, +} + +impl fmt::Display for Identity { + /// Display a specification identity. + /// + /// # use apollo_federation::link::spec::Identity; + /// use apollo_compiler::name; + /// assert_eq!( + /// Identity { domain: "https://specs.apollo.dev".to_string(), name: name!("federation") }.to_string(), + /// "https://specs.apollo.dev/federation" + /// ) + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}/{}", self.domain, self.name) + } +} + +impl Identity { + pub fn core_identity() -> Identity { + Identity { + domain: APOLLO_SPEC_DOMAIN.to_string(), + name: name!("core"), + } + } + + pub fn link_identity() -> Identity { + Identity { + domain: APOLLO_SPEC_DOMAIN.to_string(), + name: name!("link"), + } + } + + pub fn federation_identity() -> Identity { + Identity { + domain: APOLLO_SPEC_DOMAIN.to_string(), + name: name!("federation"), + } + } + + pub fn join_identity() -> Identity { + Identity { + domain: APOLLO_SPEC_DOMAIN.to_string(), + name: name!("join"), + } + } + + pub fn inaccessible_identity() -> Identity { + Identity { + domain: APOLLO_SPEC_DOMAIN.to_string(), + name: name!("inaccessible"), + } + } +} + +/// The version of a `@link` specification, in the form of a major and minor version numbers. +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] +pub struct Version { + /// The major number part of the version. + pub major: u32, + + /// The minor number part of the version. + pub minor: u32, +} + +impl fmt::Display for Version { + /// Display a specification version number. + /// + /// # use apollo_federation::link::spec::Version; + /// assert_eq!(Version { major: 2, minor: 3 }.to_string(), "2.3") + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}.{}", self.major, self.minor) + } +} + +impl str::FromStr for Version { + type Err = SpecError; + + fn from_str(s: &str) -> Result { + let (major, minor) = s.split_once('.').ok_or(SpecError::ParseError( + "version number is missing a dot (.)".to_string(), + ))?; + + let major = major.parse::().map_err(|_| { + SpecError::ParseError(format!("invalid major version number '{}'", major)) + })?; + let minor = minor.parse::().map_err(|_| { + SpecError::ParseError(format!("invalid minor version number '{}'", minor)) + })?; + + Ok(Version { major, minor }) + } +} + +impl Version { + /// Whether this version satisfies the provided `required` version. + /// + /// # use apollo_federation::link::spec::Version; + /// assert!(&Version { major: 1, minor: 0 }.satisfies(&Version{ major: 1, minor: 0 })); + /// assert!(&Version { major: 1, minor: 2 }.satisfies(&Version{ major: 1, minor: 0 })); + /// + /// assert!(!(&Version { major: 2, minor: 0 }.satisfies(&Version{ major: 1, minor: 9 }))); + /// assert!(!(&Version { major: 0, minor: 9 }.satisfies(&Version{ major: 0, minor: 8 }))); + pub fn satisfies(&self, required: &Version) -> bool { + if self.major == 0 { + self == required + } else { + self.major == required.major && self.minor >= required.minor + } + } + + /// Verifies whether this version satisfies the provided version range. + /// + /// # Panics + /// The `min` and `max` must be the same major version, and `max` minor version must be higher than `min`'s. + /// Else, you get a panic. + /// + /// # Examples + /// + /// # use apollo_federation::link::spec::Version; + /// assert!(&Version { major: 1, minor: 1 }.satisfies_range(&Version{ major: 1, minor: 0 }, &Version{ major: 1, minor: 10 })); + /// + /// assert!(!&Version { major: 2, minor: 0 }.satisfies_range(&Version{ major: 1, minor: 0 }, &Version{ major: 1, minor: 10 })); + pub fn satisfies_range(&self, min: &Version, max: &Version) -> bool { + assert_eq!(min.major, max.major); + assert!(min.minor < max.minor); + + self.major == min.major && self.minor >= min.minor && self.minor <= max.minor + } +} + +/// A `@link` specification url, which identifies a specific version of a specification. +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct Url { + /// The identity of the `@link` specification pointed by this url. + pub identity: Identity, + + /// The version of the `@link` specification pointed by this url. + pub version: Version, +} + +impl fmt::Display for Url { + /// Display a specification url. + /// + /// # use apollo_federation::link::spec::*; + /// use apollo_compiler::name; + /// assert_eq!( + /// Url { + /// identity: Identity { domain: "https://specs.apollo.dev".to_string(), name: name!("federation") }, + /// version: Version { major: 2, minor: 3 } + /// }.to_string(), + /// "https://specs.apollo.dev/federation/v2.3" + /// ) + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}/v{}", self.identity, self.version) + } +} + +impl str::FromStr for Url { + type Err = SpecError; + + fn from_str(s: &str) -> Result { + match url::Url::parse(s) { + Ok(url) => { + let mut segments = url.path_segments().ok_or(SpecError::ParseError( + "invalid `@link` specification url".to_string(), + ))?; + let version = segments.next_back().ok_or(SpecError::ParseError( + "invalid `@link` specification url: missing specification version".to_string(), + ))?; + if !version.starts_with('v') { + return Err(SpecError::ParseError("invalid `@link` specification url: the last element of the path should be the version starting with a 'v'".to_string())); + } + let version = version.strip_prefix('v').unwrap().parse::()?; + let name = segments + .next_back() + .ok_or(SpecError::ParseError( + "invalid `@link` specification url: missing specification name".to_string(), + )) + // Note this is SUPER wrong, but the JS federation implementation didn't check + // if the name was valid, and customers are actively using URLs with for example dashes. + // So we pretend that it's fine. You can't reference an imported element by the + // namespaced name because it's not valid GraphQL to do so--but you can + // explicitly import elements from a spec with an invalid name. + .map(|segment| Name::new_unchecked(segment.into()))?; + let scheme = url.scheme(); + if !scheme.starts_with("http") { + return Err(SpecError::ParseError("invalid `@link` specification url: only http(s) urls are supported currently".to_string())); + } + let url_domain = url.domain().ok_or(SpecError::ParseError( + "invalid `@link` specification url".to_string(), + ))?; + let path_remainder = segments.collect::>(); + let domain = if path_remainder.is_empty() { + format!("{}://{}", scheme, url_domain) + } else { + format!("{}://{}/{}", scheme, url_domain, path_remainder.join("/")) + }; + Ok(Url { + identity: Identity { domain, name }, + version, + }) + } + Err(e) => Err(SpecError::ParseError(format!( + "invalid specification url: {}", + e + ))), + } + } +} + +#[cfg(test)] +mod tests { + use apollo_compiler::name; + + use super::*; + + #[test] + fn versions_compares_correctly() { + assert!(Version { major: 0, minor: 0 } < Version { major: 0, minor: 1 }); + assert!(Version { major: 1, minor: 1 } < Version { major: 1, minor: 4 }); + assert!(Version { major: 1, minor: 4 } < Version { major: 2, minor: 0 }); + + assert_eq!( + Version { major: 0, minor: 0 }, + Version { major: 0, minor: 0 } + ); + assert_eq!( + Version { major: 2, minor: 3 }, + Version { major: 2, minor: 3 } + ); + } + + #[test] + fn valid_versions_can_be_parsed() { + assert_eq!( + "0.0".parse::().unwrap(), + Version { major: 0, minor: 0 } + ); + assert_eq!( + "0.5".parse::().unwrap(), + Version { major: 0, minor: 5 } + ); + assert_eq!( + "2.49".parse::().unwrap(), + Version { + major: 2, + minor: 49 + } + ); + } + + #[test] + fn invalid_versions_strings_return_menaingful_errors() { + assert_eq!( + "foo".parse::(), + Err(SpecError::ParseError( + "version number is missing a dot (.)".to_string() + )) + ); + assert_eq!( + "foo.bar".parse::(), + Err(SpecError::ParseError( + "invalid major version number 'foo'".to_string() + )) + ); + assert_eq!( + "0.bar".parse::(), + Err(SpecError::ParseError( + "invalid minor version number 'bar'".to_string() + )) + ); + assert_eq!( + "0.12-foo".parse::(), + Err(SpecError::ParseError( + "invalid minor version number '12-foo'".to_string() + )) + ); + assert_eq!( + "0.12.2".parse::(), + Err(SpecError::ParseError( + "invalid minor version number '12.2'".to_string() + )) + ); + } + + #[test] + fn valid_urls_can_be_parsed() { + assert_eq!( + "https://specs.apollo.dev/federation/v2.3" + .parse::() + .unwrap(), + Url { + identity: Identity { + domain: "https://specs.apollo.dev".to_string(), + name: name!("federation") + }, + version: Version { major: 2, minor: 3 } + } + ); + + assert_eq!( + "http://something.com/more/path/my_spec_name/v0.1?k=2" + .parse::() + .unwrap(), + Url { + identity: Identity { + domain: "http://something.com/more/path".to_string(), + name: name!("my_spec_name") + }, + version: Version { major: 0, minor: 1 } + } + ); + } +} diff --git a/apollo-federation/src/link/spec_definition.rs b/apollo-federation/src/link/spec_definition.rs new file mode 100644 index 0000000000..8ba7e01836 --- /dev/null +++ b/apollo-federation/src/link/spec_definition.rs @@ -0,0 +1,188 @@ +use std::collections::btree_map::Keys; +use std::collections::BTreeMap; +use std::sync::Arc; + +use apollo_compiler::schema::DirectiveDefinition; +use apollo_compiler::schema::ExtendedType; +use apollo_compiler::schema::Name; +use apollo_compiler::Node; + +use crate::error::FederationError; +use crate::error::SingleFederationError; +use crate::link::spec::Identity; +use crate::link::spec::Url; +use crate::link::spec::Version; +use crate::link::Link; +use crate::schema::FederationSchema; + +pub(crate) trait SpecDefinition { + fn url(&self) -> &Url; + fn minimum_federation_version(&self) -> Option<&Version>; + + fn identity(&self) -> &Identity { + &self.url().identity + } + + fn version(&self) -> &Version { + &self.url().version + } + + fn is_spec_directive_name( + &self, + schema: &FederationSchema, + name_in_schema: &Name, + ) -> Result { + let Some(metadata) = schema.metadata() else { + return Err(SingleFederationError::Internal { + message: "Schema is not a core schema (add @link first)".to_owned(), + } + .into()); + }; + Ok(metadata + .source_link_of_directive(name_in_schema) + .map(|e| e.link.url.identity == *self.identity()) + .unwrap_or(false)) + } + + fn is_spec_type_name( + &self, + schema: &FederationSchema, + name_in_schema: &Name, + ) -> Result { + let Some(metadata) = schema.metadata() else { + return Err(SingleFederationError::Internal { + message: "Schema is not a core schema (add @link first)".to_owned(), + } + .into()); + }; + Ok(metadata + .source_link_of_type(name_in_schema) + .map(|e| e.link.url.identity == *self.identity()) + .unwrap_or(false)) + } + + fn directive_name_in_schema( + &self, + schema: &FederationSchema, + name_in_spec: &Name, + ) -> Result, FederationError> { + let Some(link) = self.link_in_schema(schema)? else { + return Ok(None); + }; + Ok(Some(link.directive_name_in_schema(name_in_spec))) + } + + fn type_name_in_schema( + &self, + schema: &FederationSchema, + name_in_spec: &Name, + ) -> Result, FederationError> { + let Some(link) = self.link_in_schema(schema)? else { + return Ok(None); + }; + Ok(Some(link.type_name_in_schema(name_in_spec))) + } + + fn directive_definition<'schema>( + &self, + schema: &'schema FederationSchema, + name_in_spec: &Name, + ) -> Result>, FederationError> { + match self.directive_name_in_schema(schema, name_in_spec)? { + Some(name) => schema + .schema() + .directive_definitions + .get(&name) + .ok_or_else(|| { + SingleFederationError::Internal { + message: format!( + "Unexpectedly could not find spec directive \"@{}\" in schema", + name + ), + } + .into() + }) + .map(Some), + None => Ok(None), + } + } + + fn type_definition<'schema>( + &self, + schema: &'schema FederationSchema, + name_in_spec: &Name, + ) -> Result, FederationError> { + match self.type_name_in_schema(schema, name_in_spec)? { + Some(name) => schema + .schema() + .types + .get(&name) + .ok_or_else(|| { + SingleFederationError::Internal { + message: format!( + "Unexpectedly could not find spec type \"{}\" in schema", + name + ), + } + .into() + }) + .map(Some), + None => Ok(None), + } + } + + fn link_in_schema( + &self, + schema: &FederationSchema, + ) -> Result>, FederationError> { + let Some(metadata) = schema.metadata() else { + return Err(SingleFederationError::Internal { + message: "Schema is not a core schema (add @link first)".to_owned(), + } + .into()); + }; + Ok(metadata.for_identity(self.identity())) + } + + fn to_string(&self) -> String { + self.url().to_string() + } +} + +#[derive(Clone)] +pub(crate) struct SpecDefinitions { + identity: Identity, + definitions: BTreeMap, +} + +impl SpecDefinitions { + pub(crate) fn new(identity: Identity) -> Self { + Self { + identity, + definitions: BTreeMap::new(), + } + } + + pub(crate) fn add(&mut self, definition: T) { + assert_eq!( + *definition.identity(), + self.identity, + "Cannot add definition for {} to the versions of definitions for {}", + definition.to_string(), + self.identity + ); + if self.definitions.contains_key(definition.version()) { + return; + } + self.definitions + .insert(definition.version().clone(), definition); + } + + pub(crate) fn find(&self, requested: &Version) -> Option<&T> { + self.definitions.get(requested) + } + + pub(crate) fn versions(&self) -> Keys { + self.definitions.keys() + } +} diff --git a/apollo-federation/src/merge.rs b/apollo-federation/src/merge.rs new file mode 100644 index 0000000000..602dae37a8 --- /dev/null +++ b/apollo-federation/src/merge.rs @@ -0,0 +1,1244 @@ +use std::collections::HashSet; +use std::fmt::Debug; +use std::fmt::Formatter; +use std::iter; +use std::sync::Arc; + +use apollo_compiler::ast::Argument; +use apollo_compiler::ast::Directive; +use apollo_compiler::ast::DirectiveDefinition; +use apollo_compiler::ast::DirectiveList; +use apollo_compiler::ast::DirectiveLocation; +use apollo_compiler::ast::EnumValueDefinition; +use apollo_compiler::ast::FieldDefinition; +use apollo_compiler::ast::NamedType; +use apollo_compiler::ast::Value; +use apollo_compiler::name; +use apollo_compiler::schema::Component; +use apollo_compiler::schema::EnumType; +use apollo_compiler::schema::ExtendedType; +use apollo_compiler::schema::InputObjectType; +use apollo_compiler::schema::InputValueDefinition; +use apollo_compiler::schema::InterfaceType; +use apollo_compiler::schema::Name; +use apollo_compiler::schema::ObjectType; +use apollo_compiler::schema::ScalarType; +use apollo_compiler::schema::UnionType; +use apollo_compiler::ty; +use apollo_compiler::validation::Valid; +use apollo_compiler::Node; +use apollo_compiler::NodeStr; +use apollo_compiler::Schema; +use indexmap::map::Entry::Occupied; +use indexmap::map::Entry::Vacant; +use indexmap::map::Iter; +use indexmap::IndexMap; +use indexmap::IndexSet; + +use crate::subgraph::ValidSubgraph; + +type MergeWarning = &'static str; +type MergeError = &'static str; + +struct Merger { + errors: Vec, + composition_hints: Vec, +} + +pub struct MergeSuccess { + pub schema: Valid, + pub composition_hints: Vec, +} + +pub struct MergeFailure { + pub schema: Option, + pub errors: Vec, + pub composition_hints: Vec, +} + +impl Debug for MergeFailure { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { + f.debug_struct("MergeFailure") + .field("errors", &self.errors) + .field("composition_hints", &self.composition_hints) + .finish() + } +} + +pub fn merge_subgraphs(subgraphs: Vec<&ValidSubgraph>) -> Result { + let mut merger = Merger::new(); + merger.merge(subgraphs) +} + +impl Merger { + fn new() -> Self { + Merger { + composition_hints: Vec::new(), + errors: Vec::new(), + } + } + fn merge(&mut self, mut subgraphs: Vec<&ValidSubgraph>) -> Result { + subgraphs.sort_by(|s1, s2| s1.name.cmp(&s2.name)); + let mut subgraphs_and_enum_values: Vec<(&ValidSubgraph, Name)> = Vec::new(); + for subgraph in &subgraphs { + // TODO: Implement JS codebase's name transform (which always generates a valid GraphQL + // name and avoids collisions). + if let Ok(subgraph_name) = Name::new(&subgraph.name.to_uppercase()) { + subgraphs_and_enum_values.push((*subgraph, subgraph_name)); + } else { + self.errors + .push("Subgraph name couldn't be transformed into valid GraphQL name"); + } + } + if !self.errors.is_empty() { + return Err(MergeFailure { + schema: None, + composition_hints: self.composition_hints.to_owned(), + errors: self.errors.to_owned(), + }); + } + + let mut supergraph = Schema::new(); + // TODO handle @compose + + // add core features + // TODO verify federation versions across subgraphs + add_core_feature_link(&mut supergraph); + add_core_feature_join(&mut supergraph, &subgraphs_and_enum_values); + + // create stubs + for (subgraph, subgraph_name) in &subgraphs_and_enum_values { + let sources = Arc::make_mut(&mut supergraph.sources); + for (key, source) in subgraph.schema.sources.iter() { + sources.entry(*key).or_insert_with(|| source.clone()); + } + + self.merge_schema(&mut supergraph, subgraph); + // TODO merge directives + + for (key, value) in &subgraph.schema.types { + if value.is_built_in() || !is_mergeable_type(key) { + // skip built-ins and federation specific types + continue; + } + + match value { + ExtendedType::Enum(value) => self.merge_enum_type( + &mut supergraph.types, + subgraph_name.clone(), + key.clone(), + value, + ), + ExtendedType::InputObject(value) => self.merge_input_object_type( + &mut supergraph.types, + subgraph_name.clone(), + key.clone(), + value, + ), + ExtendedType::Interface(value) => self.merge_interface_type( + &mut supergraph.types, + subgraph_name.clone(), + key.clone(), + value, + ), + ExtendedType::Object(value) => self.merge_object_type( + &mut supergraph.types, + subgraph_name.clone(), + key.clone(), + value, + ), + ExtendedType::Union(value) => self.merge_union_type( + &mut supergraph.types, + subgraph_name.clone(), + key.clone(), + value, + ), + ExtendedType::Scalar(_value) => { + // DO NOTHING + } + } + } + + // merge executable directives + for (_, directive) in subgraph.schema.directive_definitions.iter() { + if is_executable_directive(directive) { + merge_directive(&mut supergraph.directive_definitions, directive); + } + } + } + + if self.errors.is_empty() { + // TODO: validate here and extend `MergeFailure` to propagate validation errors + let supergraph = Valid::assume_valid(supergraph); + Ok(MergeSuccess { + schema: supergraph, + composition_hints: self.composition_hints.to_owned(), + }) + } else { + Err(MergeFailure { + schema: Some(supergraph), + composition_hints: self.composition_hints.to_owned(), + errors: self.errors.to_owned(), + }) + } + } + + fn merge_descriptions(&mut self, merged: &mut Option, new: &Option) { + match (&mut *merged, new) { + (_, None) => {} + (None, Some(_)) => merged.clone_from(new), + (Some(a), Some(b)) => { + if a != b { + // TODO add info about type and from/to subgraph + self.composition_hints.push("conflicting descriptions"); + } + } + } + } + + fn merge_schema(&mut self, supergraph_schema: &mut Schema, subgraph: &ValidSubgraph) { + let supergraph_def = &mut supergraph_schema.schema_definition.make_mut(); + let subgraph_def = &subgraph.schema.schema_definition; + self.merge_descriptions(&mut supergraph_def.description, &subgraph_def.description); + + if subgraph_def.query.is_some() { + supergraph_def.query.clone_from(&subgraph_def.query); + // TODO mismatch on query types + } + if subgraph_def.mutation.is_some() { + supergraph_def.mutation.clone_from(&subgraph_def.mutation); + // TODO mismatch on mutation types + } + if subgraph_def.subscription.is_some() { + supergraph_def + .subscription + .clone_from(&subgraph_def.subscription); + // TODO mismatch on subscription types + } + } + + fn merge_enum_type( + &mut self, + types: &mut IndexMap, + subgraph_name: Name, + enum_name: NamedType, + enum_type: &Node, + ) { + let existing_type = types + .entry(enum_name.clone()) + .or_insert(copy_enum_type(enum_name, enum_type)); + if let ExtendedType::Enum(e) = existing_type { + let join_type_directives = + join_type_applied_directive(subgraph_name.clone(), iter::empty(), false); + e.make_mut().directives.extend(join_type_directives); + + self.merge_descriptions(&mut e.make_mut().description, &enum_type.description); + + // TODO we need to merge those fields LAST so we know whether enum is used as input/output/both as different merge rules will apply + // below logic only works for output enums + for (enum_value_name, enum_value) in enum_type.values.iter() { + let ev = e + .make_mut() + .values + .entry(enum_value_name.clone()) + .or_insert(Component::new(EnumValueDefinition { + value: enum_value.value.clone(), + description: None, + directives: Default::default(), + })); + self.merge_descriptions(&mut ev.make_mut().description, &enum_value.description); + ev.make_mut().directives.push(Node::new(Directive { + name: name!("join__enumValue"), + arguments: vec![ + (Node::new(Argument { + name: name!("graph"), + value: Node::new(Value::Enum(subgraph_name.clone())), + })), + ], + })); + } + } else { + // TODO - conflict + } + } + + fn merge_input_object_type( + &mut self, + types: &mut IndexMap, + subgraph_name: Name, + input_object_name: NamedType, + input_object: &Node, + ) { + let existing_type = types + .entry(input_object_name.clone()) + .or_insert(copy_input_object_type(input_object_name, input_object)); + if let ExtendedType::InputObject(obj) = existing_type { + let join_type_directives = + join_type_applied_directive(subgraph_name, iter::empty(), false); + let mutable_object = obj.make_mut(); + mutable_object.directives.extend(join_type_directives); + + for (field_name, _field) in input_object.fields.iter() { + let existing_field = mutable_object.fields.entry(field_name.clone()); + match existing_field { + Vacant(_i) => { + // TODO warning - mismatch on input fields + } + Occupied(_i) => { + // merge_options(&i.get_mut().description, &field.description); + // TODO check description + // TODO check type + // TODO check default value + // TODO process directives + } + } + } + } else { + // TODO conflict on type + } + } + + fn merge_interface_type( + &mut self, + types: &mut IndexMap, + subgraph_name: Name, + interface_name: NamedType, + interface: &Node, + ) { + let existing_type = types + .entry(interface_name.clone()) + .or_insert(copy_interface_type(interface_name, interface)); + if let ExtendedType::Interface(intf) = existing_type { + let key_directives = interface.directives.get_all("key"); + let join_type_directives = + join_type_applied_directive(subgraph_name, key_directives, false); + let mutable_intf = intf.make_mut(); + mutable_intf.directives.extend(join_type_directives); + + for (field_name, field) in interface.fields.iter() { + let existing_field = mutable_intf.fields.entry(field_name.clone()); + match existing_field { + Vacant(i) => { + // TODO warning mismatch missing fields + i.insert(Component::new(FieldDefinition { + name: field.name.clone(), + description: field.description.clone(), + arguments: vec![], + ty: field.ty.clone(), + directives: Default::default(), + })); + } + Occupied(_i) => { + // TODO check description + // TODO check type + // TODO check default value + // TODO process directives + } + } + } + } else { + // TODO conflict on type + } + } + + fn merge_object_type( + &mut self, + types: &mut IndexMap, + subgraph_name: Name, + object_name: NamedType, + object: &Node, + ) { + let is_interface_object = object.directives.has("interfaceObject"); + let existing_type = types + .entry(object_name.clone()) + .or_insert(copy_object_type_stub( + object_name.clone(), + object, + is_interface_object, + )); + if let ExtendedType::Object(obj) = existing_type { + let key_fields: HashSet<&str> = parse_keys(object.directives.get_all("key")); + let is_join_field = !key_fields.is_empty() || object_name == "Query"; + let key_directives = object.directives.get_all("key"); + let join_type_directives = + join_type_applied_directive(subgraph_name.clone(), key_directives, false); + let mutable_object = obj.make_mut(); + mutable_object.directives.extend(join_type_directives); + self.merge_descriptions(&mut mutable_object.description, &object.description); + object.implements_interfaces.iter().for_each(|intf_name| { + // IndexSet::insert deduplicates + mutable_object + .implements_interfaces + .insert(intf_name.clone()); + let join_implements_directive = + join_type_implements(subgraph_name.clone(), intf_name); + mutable_object.directives.push(join_implements_directive); + }); + + for (field_name, field) in object.fields.iter() { + // skip federation built-in queries + if field_name == "_service" || field_name == "_entities" { + continue; + } + + let existing_field = mutable_object.fields.entry(field_name.clone()); + let supergraph_field = match existing_field { + Occupied(f) => { + // check description + // check type + // check args + f.into_mut() + } + Vacant(f) => f.insert(Component::new(FieldDefinition { + name: field.name.clone(), + description: field.description.clone(), + arguments: vec![], + directives: Default::default(), + ty: field.ty.clone(), + })), + }; + self.merge_descriptions( + &mut supergraph_field.make_mut().description, + &field.description, + ); + for arg in field.arguments.iter() { + if let Some(_existing_arg) = supergraph_field.argument_by_name(&arg.name) { + } else { + // TODO mismatch no args + } + } + + if is_join_field { + let is_key_field = key_fields.contains(field_name.as_str()); + if !is_key_field { + let requires_directive_option = + Option::and_then(field.directives.get_all("requires").next(), |p| { + let requires_fields = + directive_string_arg_value(p, &name!("fields")).unwrap(); + Some(requires_fields.as_str()) + }); + let provides_directive_option = + Option::and_then(field.directives.get_all("provides").next(), |p| { + let provides_fields = + directive_string_arg_value(p, &name!("fields")).unwrap(); + Some(provides_fields.as_str()) + }); + let external_field = field.directives.get_all("external").next().is_some(); + let join_field_directive = join_field_applied_directive( + subgraph_name.clone(), + requires_directive_option, + provides_directive_option, + external_field, + ); + + supergraph_field + .make_mut() + .directives + .push(Node::new(join_field_directive)); + } + } + } + } else if let ExtendedType::Interface(intf) = existing_type { + // TODO support interface object + let key_directives = object.directives.get_all("key"); + let join_type_directives = + join_type_applied_directive(subgraph_name, key_directives, true); + intf.make_mut().directives.extend(join_type_directives); + }; + // TODO merge fields + } + + fn merge_union_type( + &mut self, + types: &mut IndexMap, + subgraph_name: Name, + union_name: NamedType, + union: &Node, + ) { + let existing_type = types.entry(union_name.clone()).or_insert(copy_union_type( + union_name.clone(), + union.description.clone(), + )); + if let ExtendedType::Union(u) = existing_type { + let join_type_directives = + join_type_applied_directive(subgraph_name.clone(), iter::empty(), false); + u.make_mut().directives.extend(join_type_directives); + + for union_member in union.members.iter() { + // IndexSet::insert deduplicates + u.make_mut().members.insert(union_member.clone()); + u.make_mut().directives.push(Component::new(Directive { + name: name!("join__unionMember"), + arguments: vec![ + Node::new(Argument { + name: name!("graph"), + value: Node::new(Value::Enum(subgraph_name.clone())), + }), + Node::new(Argument { + name: name!("member"), + value: Node::new(Value::String(NodeStr::new(union_member))), + }), + ], + })); + } + } + } +} + +const EXECUTABLE_DIRECTIVE_LOCATIONS: [DirectiveLocation; 8] = [ + DirectiveLocation::Query, + DirectiveLocation::Mutation, + DirectiveLocation::Subscription, + DirectiveLocation::Field, + DirectiveLocation::FragmentDefinition, + DirectiveLocation::FragmentSpread, + DirectiveLocation::InlineFragment, + DirectiveLocation::VariableDefinition, +]; +fn is_executable_directive(directive: &Node) -> bool { + directive + .locations + .iter() + .any(|loc| EXECUTABLE_DIRECTIVE_LOCATIONS.contains(loc)) +} + +// TODO handle federation specific types - skip if any of the link/fed spec +// TODO this info should be coming from other module +const FEDERATION_TYPES: [&str; 4] = ["_Any", "_Entity", "_Service", "@key"]; +fn is_mergeable_type(type_name: &str) -> bool { + if type_name.starts_with("federation__") || type_name.starts_with("link__") { + return false; + } + !FEDERATION_TYPES.contains(&type_name) +} + +fn copy_enum_type(enum_name: Name, enum_type: &Node) -> ExtendedType { + ExtendedType::Enum(Node::new(EnumType { + description: enum_type.description.clone(), + name: enum_name, + directives: Default::default(), + values: IndexMap::new(), + })) +} + +fn copy_input_object_type( + input_object_name: Name, + input_object: &Node, +) -> ExtendedType { + let mut new_input_object = InputObjectType { + description: input_object.description.clone(), + name: input_object_name, + directives: Default::default(), + fields: IndexMap::new(), + }; + + for (field_name, input_field) in input_object.fields.iter() { + new_input_object.fields.insert( + field_name.clone(), + Component::new(InputValueDefinition { + name: input_field.name.clone(), + description: input_field.description.clone(), + directives: Default::default(), + ty: input_field.ty.clone(), + default_value: input_field.default_value.clone(), + }), + ); + } + + ExtendedType::InputObject(Node::new(new_input_object)) +} + +fn copy_interface_type(interface_name: Name, interface: &Node) -> ExtendedType { + let new_interface = InterfaceType { + description: interface.description.clone(), + name: interface_name, + directives: Default::default(), + fields: copy_fields(interface.fields.iter()), + implements_interfaces: interface.implements_interfaces.clone(), + }; + ExtendedType::Interface(Node::new(new_interface)) +} + +fn copy_object_type_stub( + object_name: Name, + object: &Node, + is_interface_object: bool, +) -> ExtendedType { + if is_interface_object { + let new_interface = InterfaceType { + description: object.description.clone(), + name: object_name, + directives: Default::default(), + fields: copy_fields(object.fields.iter()), + implements_interfaces: object.implements_interfaces.clone(), + }; + ExtendedType::Interface(Node::new(new_interface)) + } else { + let new_object = ObjectType { + description: object.description.clone(), + name: object_name, + directives: Default::default(), + fields: copy_fields(object.fields.iter()), + implements_interfaces: object.implements_interfaces.clone(), + }; + ExtendedType::Object(Node::new(new_object)) + } +} + +fn copy_fields( + fields_to_copy: Iter>, +) -> IndexMap> { + let mut new_fields: IndexMap> = IndexMap::new(); + for (field_name, field) in fields_to_copy { + // skip federation built-in queries + if field_name == "_service" || field_name == "_entities" { + continue; + } + let args: Vec> = field + .arguments + .iter() + .map(|a| { + Node::new(InputValueDefinition { + name: a.name.clone(), + description: a.description.clone(), + directives: Default::default(), + ty: a.ty.clone(), + default_value: a.default_value.clone(), + }) + }) + .collect(); + let new_field = Component::new(FieldDefinition { + name: field.name.clone(), + description: field.description.clone(), + directives: Default::default(), + arguments: args, + ty: field.ty.clone(), + }); + + new_fields.insert(field_name.clone(), new_field); + } + new_fields +} + +fn copy_union_type(union_name: Name, description: Option) -> ExtendedType { + ExtendedType::Union(Node::new(UnionType { + description, + name: union_name, + directives: Default::default(), + members: IndexSet::new(), + })) +} + +fn join_type_applied_directive<'a>( + subgraph_name: Name, + key_directives: impl Iterator> + Sized, + is_interface_object: bool, +) -> Vec> { + let mut join_type_directive = Directive { + name: name!("join__type"), + arguments: vec![Node::new(Argument { + name: name!("graph"), + value: Node::new(Value::Enum(subgraph_name)), + })], + }; + if is_interface_object { + join_type_directive.arguments.push(Node::new(Argument { + name: name!("isInterfaceObject"), + value: Node::new(Value::Boolean(is_interface_object)), + })); + } + + let mut result = vec![]; + for key_directive in key_directives { + let mut join_type_directive_with_key = join_type_directive.clone(); + let field_set = directive_string_arg_value(key_directive, &name!("fields")).unwrap(); + join_type_directive_with_key + .arguments + .push(Node::new(Argument { + name: name!("key"), + value: Node::new(Value::String(NodeStr::new(field_set.as_str()))), + })); + + let resolvable = + directive_bool_arg_value(key_directive, &name!("resolvable")).unwrap_or(&true); + if !resolvable { + join_type_directive_with_key + .arguments + .push(Node::new(Argument { + name: name!("resolvable"), + value: Node::new(Value::Boolean(false)), + })); + } + result.push(join_type_directive_with_key) + } + if result.is_empty() { + result.push(join_type_directive) + } + result + .into_iter() + .map(Component::new) + .collect::>>() +} + +fn join_type_implements(subgraph_name: Name, intf_name: &Name) -> Component { + Component::new(Directive { + name: name!("join__implements"), + arguments: vec![ + Node::new(Argument { + name: name!("graph"), + value: Node::new(Value::Enum(subgraph_name)), + }), + Node::new(Argument { + name: name!("interface"), + value: Node::new(Value::String(intf_name.to_string().into())), + }), + ], + }) +} + +fn directive_arg_value<'a>(directive: &'a Directive, arg_name: &Name) -> Option<&'a Value> { + directive + .arguments + .iter() + .find(|arg| arg.name == *arg_name) + .map(|arg| arg.value.as_ref()) +} + +fn directive_string_arg_value<'a>( + directive: &'a Directive, + arg_name: &Name, +) -> Option<&'a NodeStr> { + match directive_arg_value(directive, arg_name) { + Some(Value::String(value)) => Some(value), + _ => None, + } +} + +fn directive_bool_arg_value<'a>(directive: &'a Directive, arg_name: &Name) -> Option<&'a bool> { + match directive_arg_value(directive, arg_name) { + Some(Value::Boolean(value)) => Some(value), + _ => None, + } +} + +// TODO link spec +fn add_core_feature_link(supergraph: &mut Schema) { + // @link(url: "https://specs.apollo.dev/link/v1.0") + supergraph + .schema_definition + .make_mut() + .directives + .push(Component::new(Directive { + name: name!("link"), + arguments: vec![Node::new(Argument { + name: name!("url"), + value: Node::new(Value::String(NodeStr::new( + "https://specs.apollo.dev/link/v1.0", + ))), + })], + })); + + let (name, link_purpose_enum) = link_purpose_enum_type(); + supergraph.types.insert(name, link_purpose_enum.into()); + + // scalar Import + let link_import_name = name!("link__Import"); + let link_import_scalar = ExtendedType::Scalar(Node::new(ScalarType { + directives: Default::default(), + name: link_import_name.clone(), + description: None, + })); + supergraph + .types + .insert(link_import_name, link_import_scalar); + + let link_directive_definition = link_directive_definition(); + supergraph + .directive_definitions + .insert(name!("link"), Node::new(link_directive_definition)); +} + +/// directive @link(url: String, as: String, import: [Import], for: link__Purpose) repeatable on SCHEMA +fn link_directive_definition() -> DirectiveDefinition { + DirectiveDefinition { + name: name!("link"), + description: None, + arguments: vec![ + Node::new(InputValueDefinition { + name: name!("url"), + description: None, + directives: Default::default(), + ty: ty!(String).into(), + default_value: None, + }), + Node::new(InputValueDefinition { + name: name!("as"), + description: None, + directives: Default::default(), + ty: ty!(String).into(), + default_value: None, + }), + Node::new(InputValueDefinition { + name: name!("for"), + description: None, + directives: Default::default(), + ty: ty!(link__Purpose).into(), + default_value: None, + }), + Node::new(InputValueDefinition { + name: name!("import"), + description: None, + directives: Default::default(), + ty: ty!([link__Import]).into(), + default_value: None, + }), + ], + locations: vec![DirectiveLocation::Schema], + repeatable: true, + } +} + +/// enum link__Purpose { +/// """ +/// \`SECURITY\` features provide metadata necessary to securely resolve fields. +/// """ +/// SECURITY +/// +/// """ +/// \`EXECUTION\` features provide metadata necessary for operation execution. +/// """ +/// EXECUTION +/// } +fn link_purpose_enum_type() -> (Name, EnumType) { + let link_purpose_name = name!("link__Purpose"); + let mut link_purpose_enum = EnumType { + description: None, + name: link_purpose_name.clone(), + directives: Default::default(), + values: IndexMap::new(), + }; + let link_purpose_security_value = EnumValueDefinition { + description: Some(NodeStr::new( + r"SECURITY features provide metadata necessary to securely resolve fields.", + )), + directives: Default::default(), + value: name!("SECURITY"), + }; + let link_purpose_execution_value = EnumValueDefinition { + description: Some(NodeStr::new( + r"EXECUTION features provide metadata necessary for operation execution.", + )), + directives: Default::default(), + value: name!("EXECUTION"), + }; + link_purpose_enum.values.insert( + link_purpose_security_value.value.clone(), + Component::new(link_purpose_security_value), + ); + link_purpose_enum.values.insert( + link_purpose_execution_value.value.clone(), + Component::new(link_purpose_execution_value), + ); + (link_purpose_name, link_purpose_enum) +} + +// TODO join spec +fn add_core_feature_join( + supergraph: &mut Schema, + subgraphs_and_enum_values: &Vec<(&ValidSubgraph, Name)>, +) { + // @link(url: "https://specs.apollo.dev/join/v0.3", for: EXECUTION) + supergraph + .schema_definition + .make_mut() + .directives + .push(Component::new(Directive { + name: name!("link"), + arguments: vec![ + Node::new(Argument { + name: name!("url"), + value: Node::new(Value::String(NodeStr::new( + "https://specs.apollo.dev/join/v0.3", + ))), + }), + Node::new(Argument { + name: name!("for"), + value: Node::new(Value::Enum(name!("EXECUTION"))), + }), + ], + })); + + // scalar FieldSet + let join_field_set_name = name!("join__FieldSet"); + let join_field_set_scalar = ExtendedType::Scalar(Node::new(ScalarType { + directives: Default::default(), + name: join_field_set_name.clone(), + description: None, + })); + supergraph + .types + .insert(join_field_set_name, join_field_set_scalar); + + let join_graph_directive_definition = join_graph_directive_definition(); + supergraph.directive_definitions.insert( + join_graph_directive_definition.name.clone(), + Node::new(join_graph_directive_definition), + ); + + let join_type_directive_definition = join_type_directive_definition(); + supergraph.directive_definitions.insert( + join_type_directive_definition.name.clone(), + Node::new(join_type_directive_definition), + ); + + let join_field_directive_definition = join_field_directive_definition(); + supergraph.directive_definitions.insert( + join_field_directive_definition.name.clone(), + Node::new(join_field_directive_definition), + ); + + let join_implements_directive_definition = join_implements_directive_definition(); + supergraph.directive_definitions.insert( + join_implements_directive_definition.name.clone(), + Node::new(join_implements_directive_definition), + ); + + let join_union_member_directive_definition = join_union_member_directive_definition(); + supergraph.directive_definitions.insert( + join_union_member_directive_definition.name.clone(), + Node::new(join_union_member_directive_definition), + ); + + let join_enum_value_directive_definition = join_enum_value_directive_definition(); + supergraph.directive_definitions.insert( + join_enum_value_directive_definition.name.clone(), + Node::new(join_enum_value_directive_definition), + ); + + let (name, join_graph_enum_type) = join_graph_enum_type(subgraphs_and_enum_values); + supergraph.types.insert(name, join_graph_enum_type.into()); +} + +/// directive @enumValue(graph: join__Graph!) repeatable on ENUM_VALUE +fn join_enum_value_directive_definition() -> DirectiveDefinition { + DirectiveDefinition { + name: name!("join__enumValue"), + description: None, + arguments: vec![Node::new(InputValueDefinition { + name: name!("graph"), + description: None, + directives: Default::default(), + ty: ty!(join__Graph!).into(), + default_value: None, + })], + locations: vec![DirectiveLocation::EnumValue], + repeatable: true, + } +} + +/// directive @field( +/// graph: Graph, +/// requires: FieldSet, +/// provides: FieldSet, +/// type: String, +/// external: Boolean, +/// override: String, +/// usedOverridden: Boolean +/// ) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION +fn join_field_directive_definition() -> DirectiveDefinition { + DirectiveDefinition { + name: name!("join__field"), + description: None, + arguments: vec![ + Node::new(InputValueDefinition { + name: name!("graph"), + description: None, + directives: Default::default(), + ty: ty!(join__Graph).into(), + default_value: None, + }), + Node::new(InputValueDefinition { + name: name!("requires"), + description: None, + directives: Default::default(), + ty: ty!(join__FieldSet).into(), + default_value: None, + }), + Node::new(InputValueDefinition { + name: name!("provides"), + description: None, + directives: Default::default(), + ty: ty!(join__FieldSet).into(), + default_value: None, + }), + Node::new(InputValueDefinition { + name: name!("type"), + description: None, + directives: Default::default(), + ty: ty!(String).into(), + default_value: None, + }), + Node::new(InputValueDefinition { + name: name!("external"), + description: None, + directives: Default::default(), + ty: ty!(Boolean).into(), + default_value: None, + }), + Node::new(InputValueDefinition { + name: name!("override"), + description: None, + directives: Default::default(), + ty: ty!(String).into(), + default_value: None, + }), + Node::new(InputValueDefinition { + name: name!("usedOverridden"), + description: None, + directives: Default::default(), + ty: ty!(Boolean).into(), + default_value: None, + }), + ], + locations: vec![ + DirectiveLocation::FieldDefinition, + DirectiveLocation::InputFieldDefinition, + ], + repeatable: true, + } +} + +fn join_field_applied_directive( + subgraph_name: Name, + requires: Option<&str>, + provides: Option<&str>, + external: bool, +) -> Directive { + let mut join_field_directive = Directive { + name: name!("join__field"), + arguments: vec![Node::new(Argument { + name: name!("graph"), + value: Node::new(Value::Enum(subgraph_name)), + })], + }; + if let Some(required_fields) = requires { + join_field_directive.arguments.push(Node::new(Argument { + name: name!("requires"), + value: Node::new(Value::String(NodeStr::new(required_fields))), + })); + } + if let Some(provided_fields) = provides { + join_field_directive.arguments.push(Node::new(Argument { + name: name!("provides"), + value: Node::new(Value::String(NodeStr::new(provided_fields))), + })); + } + if external { + join_field_directive.arguments.push(Node::new(Argument { + name: name!("external"), + value: Node::new(Value::Boolean(external)), + })); + } + join_field_directive +} + +/// directive @graph(name: String!, url: String!) on ENUM_VALUE +fn join_graph_directive_definition() -> DirectiveDefinition { + DirectiveDefinition { + name: name!("join__graph"), + description: None, + arguments: vec![ + Node::new(InputValueDefinition { + name: name!("name"), + description: None, + directives: Default::default(), + ty: ty!(String!).into(), + default_value: None, + }), + Node::new(InputValueDefinition { + name: name!("url"), + description: None, + directives: Default::default(), + ty: ty!(String!).into(), + default_value: None, + }), + ], + locations: vec![DirectiveLocation::EnumValue], + repeatable: false, + } +} + +/// directive @implements( +/// graph: Graph!, +/// interface: String! +/// ) on OBJECT | INTERFACE +fn join_implements_directive_definition() -> DirectiveDefinition { + DirectiveDefinition { + name: name!("join__implements"), + description: None, + arguments: vec![ + Node::new(InputValueDefinition { + name: name!("graph"), + description: None, + directives: Default::default(), + ty: ty!(join__Graph!).into(), + default_value: None, + }), + Node::new(InputValueDefinition { + name: name!("interface"), + description: None, + directives: Default::default(), + ty: ty!(String!).into(), + default_value: None, + }), + ], + locations: vec![DirectiveLocation::Interface, DirectiveLocation::Object], + repeatable: true, + } +} + +/// directive @type( +/// graph: Graph!, +/// key: FieldSet, +/// extension: Boolean! = false, +/// resolvable: Boolean = true, +/// isInterfaceObject: Boolean = false +/// ) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR +fn join_type_directive_definition() -> DirectiveDefinition { + DirectiveDefinition { + name: name!("join__type"), + description: None, + arguments: vec![ + Node::new(InputValueDefinition { + name: name!("graph"), + description: None, + directives: Default::default(), + ty: ty!(join__Graph!).into(), + default_value: None, + }), + Node::new(InputValueDefinition { + name: name!("key"), + description: None, + directives: Default::default(), + ty: ty!(join__FieldSet).into(), + default_value: None, + }), + Node::new(InputValueDefinition { + name: name!("extension"), + description: None, + directives: Default::default(), + ty: ty!(Boolean!).into(), + default_value: Some(Node::new(Value::Boolean(false))), + }), + Node::new(InputValueDefinition { + name: name!("resolvable"), + description: None, + directives: Default::default(), + ty: ty!(Boolean!).into(), + default_value: Some(Node::new(Value::Boolean(true))), + }), + Node::new(InputValueDefinition { + name: name!("isInterfaceObject"), + description: None, + directives: Default::default(), + ty: ty!(Boolean!).into(), + default_value: Some(Node::new(Value::Boolean(false))), + }), + ], + locations: vec![ + DirectiveLocation::Enum, + DirectiveLocation::InputObject, + DirectiveLocation::Interface, + DirectiveLocation::Object, + DirectiveLocation::Scalar, + DirectiveLocation::Union, + ], + repeatable: true, + } +} + +/// directive @unionMember(graph: join__Graph!, member: String!) repeatable on UNION +fn join_union_member_directive_definition() -> DirectiveDefinition { + DirectiveDefinition { + name: name!("join__unionMember"), + description: None, + arguments: vec![ + Node::new(InputValueDefinition { + name: name!("graph"), + description: None, + directives: Default::default(), + ty: ty!(join__Graph!).into(), + default_value: None, + }), + Node::new(InputValueDefinition { + name: name!("member"), + description: None, + directives: Default::default(), + ty: ty!(String!).into(), + default_value: None, + }), + ], + locations: vec![DirectiveLocation::Union], + repeatable: true, + } +} + +/// enum Graph +fn join_graph_enum_type( + subgraphs_and_enum_values: &Vec<(&ValidSubgraph, Name)>, +) -> (Name, EnumType) { + let join_graph_enum_name = name!("join__Graph"); + let mut join_graph_enum_type = EnumType { + description: None, + name: join_graph_enum_name.clone(), + directives: Default::default(), + values: IndexMap::new(), + }; + for (s, subgraph_name) in subgraphs_and_enum_values { + let join_graph_applied_directive = Directive { + name: name!("join__graph"), + arguments: vec![ + (Node::new(Argument { + name: name!("name"), + value: Node::new(Value::String(NodeStr::new(s.name.as_str()))), + })), + (Node::new(Argument { + name: name!("url"), + value: Node::new(Value::String(NodeStr::new(s.url.as_str()))), + })), + ], + }; + let graph = EnumValueDefinition { + description: None, + directives: DirectiveList(vec![Node::new(join_graph_applied_directive)]), + value: subgraph_name.clone(), + }; + join_graph_enum_type + .values + .insert(graph.value.clone(), Component::new(graph)); + } + (join_graph_enum_name, join_graph_enum_type) +} + +// TODO use apollo_compiler::executable::FieldSet +fn parse_keys<'a>( + directives: impl Iterator> + Sized, +) -> HashSet<&'a str> { + HashSet::from_iter( + directives + .flat_map(|k| { + let field_set = directive_string_arg_value(k, &name!("fields")).unwrap(); + field_set.split_whitespace() + }) + .collect::>(), + ) +} + +fn merge_directive( + supergraph_directives: &mut IndexMap>, + directive: &Node, +) { + if !supergraph_directives.contains_key(&directive.name.clone()) { + supergraph_directives.insert(directive.name.clone(), directive.clone()); + } +} diff --git a/apollo-federation/src/query_graph/build_query_graph.rs b/apollo-federation/src/query_graph/build_query_graph.rs new file mode 100644 index 0000000000..bfd39e2c62 --- /dev/null +++ b/apollo-federation/src/query_graph/build_query_graph.rs @@ -0,0 +1,2331 @@ +use std::sync::Arc; + +use apollo_compiler::schema::DirectiveList as ComponentDirectiveList; +use apollo_compiler::schema::ExtendedType; +use apollo_compiler::schema::Name; +use apollo_compiler::validation::Valid; +use apollo_compiler::NodeStr; +use apollo_compiler::Schema; +use indexmap::IndexMap; +use indexmap::IndexSet; +use petgraph::graph::EdgeIndex; +use petgraph::graph::NodeIndex; +use petgraph::visit::EdgeRef; +use petgraph::Direction; +use strum::IntoEnumIterator; + +use crate::error::FederationError; +use crate::error::SingleFederationError; +use crate::link::federation_spec_definition::get_federation_spec_definition_from_subgraph; +use crate::link::federation_spec_definition::FederationSpecDefinition; +use crate::link::federation_spec_definition::KeyDirectiveArguments; +use crate::query_graph::extract_subgraphs_from_supergraph::extract_subgraphs_from_supergraph; +use crate::query_graph::QueryGraph; +use crate::query_graph::QueryGraphEdge; +use crate::query_graph::QueryGraphEdgeTransition; +use crate::query_graph::QueryGraphNode; +use crate::query_graph::QueryGraphNodeType; +use crate::query_plan::operation::merge_selection_sets; +use crate::query_plan::operation::Selection; +use crate::query_plan::operation::SelectionSet; +use crate::schema::field_set::parse_field_set; +use crate::schema::position::AbstractTypeDefinitionPosition; +use crate::schema::position::CompositeTypeDefinitionPosition; +use crate::schema::position::FieldDefinitionPosition; +use crate::schema::position::InterfaceTypeDefinitionPosition; +use crate::schema::position::ObjectFieldDefinitionPosition; +use crate::schema::position::ObjectOrInterfaceTypeDefinitionPosition; +use crate::schema::position::ObjectTypeDefinitionPosition; +use crate::schema::position::OutputTypeDefinitionPosition; +use crate::schema::position::SchemaRootDefinitionKind; +use crate::schema::position::SchemaRootDefinitionPosition; +use crate::schema::position::TypeDefinitionPosition; +use crate::schema::position::UnionTypeDefinitionPosition; +use crate::schema::ValidFederationSchema; + +/// Builds a "federated" query graph based on the provided supergraph and API schema. +/// +/// A federated query graph is one that is used to reason about queries made by a router against a +/// set of federated subgraph services. +/// +/// Assumes the given schemas have been validated. +pub fn build_federated_query_graph( + supergraph_schema: ValidFederationSchema, + api_schema: ValidFederationSchema, + // TODO(@goto-bus-stop): replace these booleans by descriptive types (either a struct or two + // enums) + validate_extracted_subgraphs: Option, + for_query_planning: Option, +) -> Result { + let for_query_planning = for_query_planning.unwrap_or(true); + let mut query_graph = QueryGraph { + // Note this name is a dummy initial name that gets overridden as we build the query graph. + current_source: NodeStr::new(""), + graph: Default::default(), + sources: Default::default(), + types_to_nodes_by_source: Default::default(), + root_kinds_to_nodes_by_source: Default::default(), + non_trivial_followup_edges: Default::default(), + }; + let subgraphs = + extract_subgraphs_from_supergraph(&supergraph_schema, validate_extracted_subgraphs)?; + for (subgraph_name, subgraph) in subgraphs { + let builder = SchemaQueryGraphBuilder::new( + query_graph, + NodeStr::new(&subgraph_name), + subgraph.schema, + Some(api_schema.clone()), + for_query_planning, + )?; + query_graph = builder.build()?; + } + let federated_builder = FederatedQueryGraphBuilder::new(query_graph, supergraph_schema)?; + query_graph = federated_builder.build()?; + Ok(query_graph) +} + +/// Builds a query graph based on the provided schema (usually an API schema outside of testing). +/// +/// Assumes the given schemas have been validated. +pub fn build_query_graph( + name: NodeStr, + schema: ValidFederationSchema, +) -> Result { + let mut query_graph = QueryGraph { + // Note this name is a dummy initial name that gets overridden as we build the query graph. + current_source: NodeStr::new(""), + graph: Default::default(), + sources: Default::default(), + types_to_nodes_by_source: Default::default(), + root_kinds_to_nodes_by_source: Default::default(), + non_trivial_followup_edges: Default::default(), + }; + let builder = SchemaQueryGraphBuilder::new(query_graph, name, schema, None, false)?; + query_graph = builder.build()?; + Ok(query_graph) +} + +struct BaseQueryGraphBuilder { + query_graph: QueryGraph, +} + +impl BaseQueryGraphBuilder { + fn new(mut query_graph: QueryGraph, source: NodeStr, schema: ValidFederationSchema) -> Self { + query_graph.current_source = source.clone(); + query_graph.sources.insert(source.clone(), schema); + query_graph + .types_to_nodes_by_source + .insert(source.clone(), IndexMap::new()); + query_graph + .root_kinds_to_nodes_by_source + .insert(source.clone(), IndexMap::new()); + Self { query_graph } + } + + fn build(self) -> QueryGraph { + self.query_graph + } + + fn add_edge( + &mut self, + head: NodeIndex, + tail: NodeIndex, + transition: QueryGraphEdgeTransition, + conditions: Option>, + ) -> Result<(), FederationError> { + self.query_graph.graph.add_edge( + head, + tail, + QueryGraphEdge { + transition, + conditions, + }, + ); + let head_weight = self.query_graph.node_weight(head)?; + let tail_weight = self.query_graph.node_weight(tail)?; + if head_weight.source != tail_weight.source { + self.mark_has_reachable_cross_subgraph_edges_for_ancestors(head)?; + } + Ok(()) + } + + fn mark_has_reachable_cross_subgraph_edges_for_ancestors( + &mut self, + from: NodeIndex, + ) -> Result<(), FederationError> { + let from_weight = self.query_graph.node_weight(from)?; + // When we mark a node, we mark all of its "ancestor" nodes, so if we get a node already + // marked, there is nothing more to do. + if from_weight.has_reachable_cross_subgraph_edges { + return Ok(()); + } + let mut stack = vec![from]; + while let Some(next) = stack.pop() { + let next_weight = self.query_graph.node_weight_mut(next)?; + next_weight.has_reachable_cross_subgraph_edges = true; + let next_weight = self.query_graph.node_weight(next)?; + for head in self + .query_graph + .graph + .neighbors_directed(next, Direction::Incoming) + { + let head_weight = self.query_graph.node_weight(head)?; + // Again, no point in redoing work as soon as we read an already-marked node. We + // also only follow in-edges within the same subgraph, as nodes on other subgraphs + // will have been marked with their own cross-subgraph edges. + if head_weight.source == next_weight.source + && !head_weight.has_reachable_cross_subgraph_edges + { + stack.push(head); + } + } + } + Ok(()) + } + + fn create_new_node(&mut self, type_: QueryGraphNodeType) -> Result { + let node = self.query_graph.graph.add_node(QueryGraphNode { + type_: type_.clone(), + source: self.query_graph.current_source.clone(), + has_reachable_cross_subgraph_edges: false, + provide_id: None, + root_kind: None, + }); + if let QueryGraphNodeType::SchemaType(pos) = type_ { + self.query_graph + .types_to_nodes_mut()? + .entry(pos.type_name().clone()) + .or_insert_with(IndexSet::new) + .insert(node); + } + Ok(node) + } + + fn create_root_node( + &mut self, + type_: QueryGraphNodeType, + root_kind: SchemaRootDefinitionKind, + ) -> Result { + let node = self.create_new_node(type_)?; + self.set_as_root(node, root_kind)?; + Ok(node) + } + + fn set_as_root( + &mut self, + node: NodeIndex, + root_kind: SchemaRootDefinitionKind, + ) -> Result<(), FederationError> { + let node_weight = self.query_graph.node_weight_mut(node)?; + node_weight.root_kind = Some(root_kind); + let root_kinds_to_nodes = self.query_graph.root_kinds_to_nodes_mut()?; + root_kinds_to_nodes.insert(root_kind, node); + Ok(()) + } +} + +struct SchemaQueryGraphBuilder { + base: BaseQueryGraphBuilder, + subgraph: Option, + for_query_planning: bool, +} + +struct SchemaQueryGraphBuilderSubgraphData { + federation_spec_definition: &'static FederationSpecDefinition, + api_schema: ValidFederationSchema, +} + +impl SchemaQueryGraphBuilder { + /// If api_schema is given, then this builder assumes the schema is a subgraph schema, and that + /// a subgraph query graph is being built. + fn new( + query_graph: QueryGraph, + source: NodeStr, + schema: ValidFederationSchema, + api_schema: Option, + for_query_planning: bool, + ) -> Result { + let subgraph = if let Some(api_schema) = api_schema { + let federation_spec_definition = get_federation_spec_definition_from_subgraph(&schema)?; + Some(SchemaQueryGraphBuilderSubgraphData { + federation_spec_definition, + api_schema, + }) + } else { + None + }; + let base = BaseQueryGraphBuilder::new(query_graph, source, schema); + Ok(SchemaQueryGraphBuilder { + base, + subgraph, + for_query_planning, + }) + } + + fn build(mut self) -> Result { + // PORT_NOTE: Note that most of the JS code's buildGraphInternal() logic was moved into this + // build() method. + for root_kind in SchemaRootDefinitionKind::iter() { + let pos = SchemaRootDefinitionPosition { root_kind }; + if pos + .try_get(self.base.query_graph.schema()?.schema()) + .is_some() + { + self.add_recursively_from_root(pos)?; + } + } + if self.subgraph.is_some() { + self.add_interface_entity_edges()?; + } + if self.for_query_planning { + self.add_additional_abstract_type_edges()?; + } + Ok(self.base.build()) + } + + fn is_external( + &self, + field_definition_position: &FieldDefinitionPosition, + ) -> Result { + if let Some(subgraph_metadata) = self.base.query_graph.schema()?.subgraph_metadata() { + Ok(subgraph_metadata + .external_metadata() + .is_external(field_definition_position)?) + } else { + Ok(false) + } + } + + /// Adds a node for the provided root object type (marking that node as a root node for the + /// provided `kind`) and recursively descends into the type definition to add the related nodes + /// and edges. + /// + /// In other words, calling this method on, say, the root query type of a schema will add nodes + /// and edges for all the types reachable from that root query type. + fn add_recursively_from_root( + &mut self, + root: SchemaRootDefinitionPosition, + ) -> Result<(), FederationError> { + let root_type_name = root.get(self.base.query_graph.schema()?.schema())?; + let pos = match self + .base + .query_graph + .schema()? + .get_type(root_type_name.name.clone())? + { + TypeDefinitionPosition::Object(pos) => pos, + _ => { + return Err(SingleFederationError::Internal { + message: format!( + "Root type \"{}\" was unexpectedly not an object type", + root_type_name.name, + ), + } + .into()); + } + }; + let node = self.add_type_recursively(pos.into())?; + self.base.set_as_root(node, root.root_kind) + } + + /// Adds in a node for the provided type in the in-building query graph, and recursively adds + /// edges and nodes corresponding to the type definition (so for object types, it will add edges + /// for each field and recursively add nodes for each field's type, etc...). + fn add_type_recursively( + &mut self, + output_type_definition_position: OutputTypeDefinitionPosition, + ) -> Result { + let type_name = output_type_definition_position.type_name().clone(); + if let Some(existing) = self.base.query_graph.types_to_nodes()?.get(&type_name) { + if let Some(first_node) = existing.first() { + return if existing.len() == 1 { + Ok(*first_node) + } else { + Err(SingleFederationError::Internal { + message: format!( + "Only one node should have been created for type \"{}\", got {}", + type_name, + existing.len(), + ), + } + .into()) + }; + } + } + let node = self + .base + .create_new_node(output_type_definition_position.clone().into())?; + match output_type_definition_position { + OutputTypeDefinitionPosition::Object(pos) => { + self.add_object_type_edges(pos, node)?; + } + OutputTypeDefinitionPosition::Interface(pos) => { + // For interfaces, we generally don't add direct edges for their fields. Because in + // general, the subgraph where a particular field can be fetched from may depend on + // the runtime implementation. However, if the subgraph we're currently including + // "provides" a particular interface field locally *for all the supergraph + // interface's implementations* (in other words, we know we can always ask the field + // to that subgraph directly on the interface and will never miss anything), then we + // can add a direct edge to the field for the interface in that subgraph (which + // avoids unnecessary type exploding in practice). + if self.subgraph.is_some() { + self.maybe_add_interface_fields_edges(pos.clone(), node)?; + } + self.add_abstract_type_edges(pos.clone().into(), node)?; + } + OutputTypeDefinitionPosition::Union(pos) => { + // Add the special-case __typename edge for unions. + self.add_edge_for_field(pos.introspection_typename_field().into(), node, false)?; + self.add_abstract_type_edges(pos.clone().into(), node)?; + } + // Any other case (scalar or enum; input objects are not possible here) is terminal and + // has no edges to consider. + _ => {} + } + Ok(node) + } + + fn add_object_type_edges( + &mut self, + object_type_definition_position: ObjectTypeDefinitionPosition, + head: NodeIndex, + ) -> Result<(), FederationError> { + let type_ = + object_type_definition_position.get(self.base.query_graph.schema()?.schema())?; + let is_interface_object = if let Some(subgraph) = &self.subgraph { + let interface_object_directive_definition = subgraph + .federation_spec_definition + .interface_object_directive_definition(self.base.query_graph.schema()?)? + .ok_or_else(|| SingleFederationError::Internal { + message: "Interface object directive definition unexpectedly missing" + .to_owned(), + })?; + type_ + .directives + .iter() + .any(|d| d.name == interface_object_directive_definition.name) + } else { + false + }; + + // Add edges to the query graph for each field. Note subgraph extraction adds the _entities + // field to subgraphs, so when we recursively handle that field on the root query type, we + // ensure that all entities are part of the graph (even if they are not reachable by any + // other user operations). + // + // Note that FederationSchema ensures there are no introspection fields in "fields", but + // we do handle __typename as a special case below. + let fields = type_.fields.keys().cloned().collect::>(); + for field_name in fields { + // Fields marked @external only exist to ensure subgraph schemas are valid GraphQL, but + // they don't create actual edges. However, even if we don't add an edge, we still want + // to add the field's type. The reason is that while we don't add a "general" edge for + // an external field, we may later add path-specific edges for the field due to a + // `@provides`. When we do so, we need the node corresponding to that field type to + // exist, and in rare cases a type could be only mentioned in this external field, so if + // we don't add the type here, we never do and get issues later when we add @provides + // edges. + let pos = object_type_definition_position.field(field_name); + let is_external = self.is_external(&pos.clone().into())?; + self.add_edge_for_field(pos.into(), head, is_external)?; + } + // We add an edge for the built-in __typename field. For instance, it's perfectly valid to + // query __typename manually, so we want to have an edge for it. + // + // However, note that @interfaceObject types are an exception to the rule of "it's perfectly + // valid to query __typename". More precisely, a query can ask for the `__typename` of + // anything, but it shouldn't be answered by an @interfaceObject and so we don't add an + // edge in that case, ensuring the query planner has to get it from another subgraph (than + // the one with said @interfaceObject). + if !is_interface_object { + let pos = object_type_definition_position.introspection_typename_field(); + self.add_edge_for_field(pos.into(), head, false)?; + } + + Ok(()) + } + + fn add_edge_for_field( + &mut self, + field_definition_position: FieldDefinitionPosition, + head: NodeIndex, + skip_edge: bool, + ) -> Result<(), FederationError> { + let field = field_definition_position.get(self.base.query_graph.schema()?.schema())?; + let tail_pos: OutputTypeDefinitionPosition = self + .base + .query_graph + .schema()? + .get_type(field.ty.inner_named_type().clone())? + .try_into()?; + let tail = self.add_type_recursively(tail_pos)?; + if !skip_edge { + let transition = QueryGraphEdgeTransition::FieldCollection { + source: self.base.query_graph.current_source.clone(), + field_definition_position, + is_part_of_provides: false, + }; + self.base.add_edge(head, tail, transition, None)?; + } + Ok(()) + } + + fn maybe_add_interface_fields_edges( + &mut self, + interface_type_definition_position: InterfaceTypeDefinitionPosition, + head: NodeIndex, + ) -> Result<(), FederationError> { + let Some(subgraph) = &self.subgraph else { + return Err(SingleFederationError::Internal { + message: "Missing subgraph data when building subgraph query graph".to_owned(), + } + .into()); + }; + // In theory, the interface might have been marked inaccessible and not be in the API + // schema. If that's the case, we just don't add direct edges at all (adding interface edges + // is an optimization and if the interface is inaccessible, it probably doesn't play any + // role in query planning anyway, so it doesn't matter). + if interface_type_definition_position + .try_get(subgraph.api_schema.schema()) + .is_none() + { + return Ok(()); + } + + let api_runtime_type_positions = subgraph + .api_schema + .referencers() + .get_interface_type(&interface_type_definition_position.type_name)? + .object_types + .clone(); + // Note that it's possible that the current subgraph does not even know some of the possible + // runtime types of the API schema. But as edges to interfaces can only come from the + // current subgraph, it does mean that whatever field led to this interface was resolved in + // this subgraph and can never return one of those unknown runtime types. So we can ignore + // them. + // + // TODO: We *must* revisit this once we fully add @key for interfaces as it will invalidate + // the "edges to interfaces can only come from the current subgraph". Most likely, _if_ an + // interface has a key, then we should return early from this function (add no field edges + // at all) if the subgraph doesn't know of at least one implementation. + let mut local_runtime_type_positions = Vec::new(); + for api_runtime_type_position in &api_runtime_type_positions { + if api_runtime_type_position + .try_get(self.base.query_graph.schema()?.schema()) + .is_some() + { + local_runtime_type_positions.push(api_runtime_type_position) + } + } + let type_ = + interface_type_definition_position.get(self.base.query_graph.schema()?.schema())?; + + // Same as for objects, we add edges to the query graph for each field. + // + // Note that FederationSchema ensures there are no introspection fields in "fields", but + // we do handle __typename as a special case below. + let fields = type_.fields.keys().cloned().collect::>(); + for field_name in fields { + // To include the field, it must not be external itself, and it must be provided on + // all of the local runtime types. + let pos = interface_type_definition_position.field(field_name.clone()); + let is_external = self.is_external(&pos.clone().into())?; + let mut is_provided_by_all_local_types = true; + for local_runtime_type in &local_runtime_type_positions { + if !self + .is_directly_provided_by_type(local_runtime_type.field(field_name.clone()))? + { + is_provided_by_all_local_types = false; + } + } + if is_external || !is_provided_by_all_local_types { + continue; + } + self.add_edge_for_field(pos.into(), head, false)?; + } + // Same as for objects, we add an edge for the built-in __typename field. + // + // Note that __typename will never be external and will always provided by all local runtime + // types, so we unconditionally add the edge here. + self.add_edge_for_field( + interface_type_definition_position + .introspection_typename_field() + .into(), + head, + false, + ) + } + + fn is_directly_provided_by_type( + &self, + object_field_definition_position: ObjectFieldDefinitionPosition, + ) -> Result { + // The field is directly provided if: + // 1) the type does have it. + // 2) it is not external. + // 3) it does not have a @requires (essentially, this method is called on type + // implementations of an interface to decide if we can avoid type-explosion, but if the + // field has a @requires on an implementation, then we need to type-explode to make + // sure we handle that @requires). + if object_field_definition_position + .try_get(self.base.query_graph.schema()?.schema()) + .is_none() + { + return Ok(false); + } + let is_external = self.is_external(&object_field_definition_position.clone().into())?; + let has_requires = if let Some(subgraph) = &self.subgraph { + let requires_directive_definition = subgraph + .federation_spec_definition + .requires_directive_definition(self.base.query_graph.schema()?)?; + object_field_definition_position + .get(self.base.query_graph.schema()?.schema())? + .directives + .iter() + .any(|d| d.name == requires_directive_definition.name) + } else { + false + }; + Ok(!is_external && !has_requires) + } + + fn add_abstract_type_edges( + &mut self, + abstract_type_definition_position: AbstractTypeDefinitionPosition, + head: NodeIndex, + ) -> Result<(), FederationError> { + let implementations = self + .base + .query_graph + .schema()? + .possible_runtime_types(abstract_type_definition_position.clone().into())?; + for pos in implementations { + let tail = self.add_type_recursively(pos.clone().into())?; + let transition = QueryGraphEdgeTransition::Downcast { + source: self.base.query_graph.current_source.clone(), + from_type_position: abstract_type_definition_position.clone().into(), + to_type_position: pos.into(), + }; + self.base.add_edge(head, tail, transition, None)?; + } + Ok(()) + } + + /// We've added edges that avoid type-explosion _directly_ from an interface, but it means that + /// so far we always type-explode unions to all their implementation types, and always + /// type-explode when we go through 2 unrelated interfaces. For instance, say we have + /// ```graphql + /// type Query { + /// i1: I1 + /// i2: I2 + /// u: U + /// } + /// + /// interface I1 { + /// x: Int + /// } + /// + /// interface I2 { + /// y: Int + /// } + /// + /// type A implements I1 & I2 { + /// x: Int + /// y: Int + /// } + /// + /// type B implements I1 & I2 { + /// x: Int + /// y: Int + /// } + /// + /// union U = A | B + /// ``` + /// If we query: + /// ```graphql + /// { + /// u { + /// ... on I1 { + /// x + /// } + /// } + /// } + /// ``` + /// then we currently have no edge between `U` and `I1` whatsoever, so query planning would have + /// to type-explode `U` even though that's not necessary (assuming everything is in the same + /// subgraph, we'd want to send the query "as-is"). + /// Same thing for: + /// ```graphql + /// { + /// i1 { + /// x + /// ... on I2 { + /// y + /// } + /// } + /// } + /// ``` + /// due to not having edges from `I1` to `I2` (granted, in that example, type-exploding is not + /// all that worse, but it gets worse with more implementations/fields). + /// + /// And so this method is about adding such edges. Essentially, every time 2 abstract types have + /// an intersection of runtime types > 1, we add an edge. + /// + /// Do note that in practice we only add those edges when we build a query graph for query + /// planning purposes, because not type-exploding is only an optimization but type-exploding + /// will always "work" and for composition validation, we don't care about being optimal, while + /// limiting edges make validation faster by limiting the choices to explore. Also, query + /// planning is careful, as it walks those edges, to compute the actual possible runtime types + /// we could have to avoid later type-exploding in impossible runtime types. + fn add_additional_abstract_type_edges(&mut self) -> Result<(), FederationError> { + // As mentioned above, we only care about this on subgraph query graphs during query + // planning. But if this ever gets called in some other code path, ignore this. + let Some(subgraph) = &self.subgraph else { + return Ok(()); + }; + + // For each abstract type in the schema, compute its runtime types. + let mut abstract_types_with_runtime_types = Vec::new(); + for (type_name, type_) in &self.base.query_graph.schema()?.schema().types { + let pos: AbstractTypeDefinitionPosition = match type_ { + ExtendedType::Interface(_) => InterfaceTypeDefinitionPosition { + type_name: type_name.clone(), + } + .into(), + ExtendedType::Union(_) => UnionTypeDefinitionPosition { + type_name: type_name.clone(), + } + .into(), + _ => continue, + }; + // All "normal" types from subgraphs should be in the API schema, but there are a + // couple exceptions: + // - Subgraphs have the `_Entity` type, which is not in the API schema. + // - Types marked @inaccessible also won't be in the API schema. + // In those cases, we don't create any additional edges for those types. For + // inaccessible types, we could theoretically try to add them, but we would need the + // full supergraph while we currently only have access to the API schema, and besides, + // inaccessible types can only be part of the query execution in indirect ways (e.g. + // through some @requires), and you'd need pretty weird @requires for the + // optimization here to ever matter. + match subgraph.api_schema.schema().types.get(type_name) { + Some(ExtendedType::Interface(_)) => {} + Some(ExtendedType::Union(_)) => {} + None => continue, + _ => { + return Err(SingleFederationError::Internal { + message: format!( + "Type \"{}\" was abstract in subgraph but not in API schema", + type_name, + ), + } + .into()); + } + } + abstract_types_with_runtime_types.push(AbstractTypeWithRuntimeTypes { + abstract_type_definition_position: pos.clone(), + subgraph_runtime_type_positions: self + .base + .query_graph + .schema()? + .possible_runtime_types(pos.clone().into())?, + api_runtime_type_positions: subgraph + .api_schema + .possible_runtime_types(pos.clone().into())?, + }); + } + + // Check every pair of abstract types that intersect on at least 2 runtime types to see if + // we have edges to add. Note that in practice, we only care about 'Union -> Interface' and + // 'Interface -> Interface'. + for (i, t1) in abstract_types_with_runtime_types.iter().enumerate() { + // Note that in general, t1 is already part of the graph, so `add_type_recursively()` + // doesn't really add anything, it just returns the existing node. That said, if t1 is + // returned by no field (at least no field reachable from a root type), that type will + // not be part of the graph. And in that case, we do add it. And it's actually + // possible that we don't create any edge to that created node, so we may be creating a + // disconnected subset of the graph, a part that is not reachable from any root. It's + // not optimal, but it's a bit hard to avoid in the first place (we could also try to + // purge such subsets after this method, but it's probably not worth it in general) and + // it's not a big deal: it will just use a bit more memory than necessary, and it's + // probably pretty rare in the first place. + let t1_node = + self.add_type_recursively(t1.abstract_type_definition_position.clone().into())?; + for (j, t2) in abstract_types_with_runtime_types.iter().enumerate() { + if j > i { + break; + } + + // PORT_NOTE: The JS code skipped cases where interfaces implemented other + // interfaces, claiming this was handled already by add_abstract_type_edges(). + // However, this was not actually handled by that function, so we fix the bug here + // by not early-returning for interfaces implementing interfaces. + + let mut add_t1_to_t2 = false; + let mut add_t2_to_t1 = false; + if t1.abstract_type_definition_position == t2.abstract_type_definition_position { + // We always add an edge from a type to itself. This is just saying that if + // we're type-casting to the type we're already on, it's doing nothing, and in + // particular it shouldn't force us to type-explode anymore that if we didn't + // have the cast in the first place. Note that we only set `add_t1_To_t2` to + // true, otherwise we'd be adding the same edge twice. + add_t1_to_t2 = true; + } else { + // Otherwise, there is 2 aspects to take into account: + // - It's only worth adding an edge between types, meaning that we might save + // type-exploding into the runtime types of the target/"to" one, if the local + // intersection (of runtime types, in the current subgraph) for the abstract + // types is more than 2. If it's just 1 type, then going to that type directly + // is not less efficient and is more precise in a sense. And if the + // intersection is empty, then no point in polluting the query graphs with + // edges we'll never take. + // - _But_ we can only save type-exploding if that local intersection does not + // exclude any runtime types that are local to the source/"from" type, not + // local to the target/"to" type, *but* are global to the target/"to" type, + // because such types should not be excluded and only type-explosion will + // achieve that (for some concrete examples, see the "merged abstract types + // handling" tests in `build_plan()` tests). In other words, we don't want to + // avoid the type explosion if there is a type in the intersection of the + // local source/"from" runtime types and global target/"to" runtime types that + // is not in the purely local runtime type intersection. + + let intersecting_local_runtime_type_positions = t1 + .subgraph_runtime_type_positions + .intersection(&t2.subgraph_runtime_type_positions) + .collect::>(); + if intersecting_local_runtime_type_positions.len() >= 2 { + let is_in_local_other_type_but_not_local_intersection = |type_pos: &ObjectTypeDefinitionPosition, other_type: &AbstractTypeWithRuntimeTypes| { + other_type.subgraph_runtime_type_positions.contains(type_pos) && + !intersecting_local_runtime_type_positions.contains(type_pos) + }; + // TODO: we're currently _never_ adding the edge if the target/"to" type is + // a union. We shouldn't be doing that, this will genuinely make some cases + // less efficient than they could be (though those cases are admittedly a + // bit convoluted), but this make sense *until* + // https://github.com/apollographql/federation/issues/2256 gets fixed. + // Because until then, we do not properly track unions through composition, + // and that means there is never a difference (in the query planner) between + // a local union definition and the supergraph one, even if that different + // actually exists. And so, never type-exploding in that case is somewhat + // safer, as not-type-exploding is ultimately an optimisation. Please note + // that this is *not* a fix for #2256, and most of the issues created by + // #2256 still needs fixing, but it avoids making it even worth for a few + // corner cases. We should remove the `isUnionType` below once the + // fix for #2256 is implemented. + if !(matches!( + t2.abstract_type_definition_position, + AbstractTypeDefinitionPosition::Union(_) + ) || t2 + .api_runtime_type_positions + .iter() + .any(|rt| is_in_local_other_type_but_not_local_intersection(rt, t1))) + { + add_t1_to_t2 = true; + } + if !(matches!( + t1.abstract_type_definition_position, + AbstractTypeDefinitionPosition::Union(_) + ) || t1 + .api_runtime_type_positions + .iter() + .any(|rt| is_in_local_other_type_but_not_local_intersection(rt, t2))) + { + add_t2_to_t1 = true; + } + } + } + + if add_t1_to_t2 || add_t2_to_t1 { + // Same remark as for t1 above. + let t2_node = self.add_type_recursively( + t2.abstract_type_definition_position.clone().into(), + )?; + if add_t1_to_t2 { + let transition = QueryGraphEdgeTransition::Downcast { + source: self.base.query_graph.current_source.clone(), + from_type_position: t1.abstract_type_definition_position.clone().into(), + to_type_position: t2.abstract_type_definition_position.clone().into(), + }; + self.base.add_edge(t1_node, t2_node, transition, None)?; + } + if add_t2_to_t1 { + let transition = QueryGraphEdgeTransition::Downcast { + source: self.base.query_graph.current_source.clone(), + from_type_position: t2.abstract_type_definition_position.clone().into(), + to_type_position: t1.abstract_type_definition_position.clone().into(), + }; + self.base.add_edge(t2_node, t1_node, transition, None)?; + } + } + } + } + + Ok(()) + } + + /// In a subgraph, all entity object types will be "automatically" reachable (from the root + /// query type) because of the `_entities` field (it returns `_Entity`, which is a union of all + /// entity object types, making those reachable. + /// + /// However, we also want entity interface types (interfaces with an @key) to be reachable in a + /// similar way, because the `_entities` field is also technically the one resolving them, and + /// not having them reachable would break plenty of code that assume that by traversing a query + /// graph from root, we get to everything that can be queried. + /// + /// But because GraphQL unions cannot have interface types, they are not part of the `_Entity` + /// union (and cannot be). This is ok as far as the typing of the schema goes, because even when + /// `_entities` is called to resolve an interface type, it technically returns a concrete + /// object, and so, since every implementation of an entity interface is also an entity, this is + /// captured by the `_Entity` union. + /// + /// But it does mean we want to manually add the corresponding edges now for interfaces, or @key + /// on interfaces wouldn't work properly (at least, when the interface is not otherwise + /// reachable by an operation on the subgraph). + fn add_interface_entity_edges(&mut self) -> Result<(), FederationError> { + let Some(subgraph) = &self.subgraph else { + return Err(SingleFederationError::Internal { + message: "Missing subgraph data when building subgraph query graph".to_owned(), + } + .into()); + }; + let federation_spec_definition = subgraph.federation_spec_definition; + let entity_type_definition = + federation_spec_definition.entity_type_definition(self.base.query_graph.schema()?)?; + // We can ignore this case because if the subgraph has an interface with an @key, then we + // force its implementations to be marked as entity too and so we know that if `_Entity` is + // undefined, then we have no need for entity edges. + let Some(entity_type_definition) = entity_type_definition else { + return Ok(()); + }; + let entity_type_name = entity_type_definition.name.clone(); + let entity_type_node = self.add_type_recursively( + UnionTypeDefinitionPosition { + type_name: entity_type_name.clone(), + } + .into(), + )?; + let key_directive_definition = + federation_spec_definition.key_directive_definition(self.base.query_graph.schema()?)?; + let mut interface_type_definition_positions = Vec::new(); + for (type_name, type_) in &self.base.query_graph.schema()?.schema().types { + let ExtendedType::Interface(type_) = type_ else { + continue; + }; + if !resolvable_key_applications( + &type_.directives, + &key_directive_definition.name, + federation_spec_definition, + )? + .is_empty() + { + interface_type_definition_positions.push(InterfaceTypeDefinitionPosition { + type_name: type_name.clone(), + }); + } + } + for interface_type_definition_position in interface_type_definition_positions { + let interface_type_node = + self.add_type_recursively(interface_type_definition_position.clone().into())?; + let transition = QueryGraphEdgeTransition::Downcast { + source: self.base.query_graph.current_source.clone(), + from_type_position: UnionTypeDefinitionPosition { + type_name: entity_type_name.clone(), + } + .into(), + to_type_position: interface_type_definition_position.into(), + }; + self.base + .add_edge(entity_type_node, interface_type_node, transition, None)?; + } + + Ok(()) + } +} + +struct AbstractTypeWithRuntimeTypes { + abstract_type_definition_position: AbstractTypeDefinitionPosition, + subgraph_runtime_type_positions: IndexSet, + api_runtime_type_positions: IndexSet, +} + +struct FederatedQueryGraphBuilder { + base: BaseQueryGraphBuilder, + supergraph_schema: ValidFederationSchema, + subgraphs: FederatedQueryGraphBuilderSubgraphs, +} + +impl FederatedQueryGraphBuilder { + fn new( + query_graph: QueryGraph, + supergraph_schema: ValidFederationSchema, + ) -> Result { + let base = BaseQueryGraphBuilder::new( + query_graph, + NodeStr::new(FEDERATED_GRAPH_ROOT_SOURCE), + // This is a dummy schema that should never be used, so it's fine if we assume validity + // here (note that empty schemas have no Query type, making them invalid GraphQL). + ValidFederationSchema::new(Valid::assume_valid(Schema::new()))?, + ); + let subgraphs = FederatedQueryGraphBuilderSubgraphs::new(&base)?; + Ok(FederatedQueryGraphBuilder { + base, + supergraph_schema, + subgraphs, + }) + } + + fn build(mut self) -> Result { + self.add_federated_root_nodes()?; + self.copy_types_to_nodes()?; + self.add_root_edges()?; + self.handle_key()?; + self.handle_requires()?; + // Note that @provides must be handled last when building since it requires copying nodes + // and their edges, and it's easier to reason about this if we know previous + self.handle_provides()?; + // The exception to the above rule is @interaceObject handling, where we explicitly don't + // want to add self-edges for copied @provides nodes. (See the comments in this method for + // more details). + self.handle_interface_object()?; + // This method adds no nodes/edges, but just precomputes followup edge information. + self.precompute_non_trivial_followup_edges()?; + Ok(self.base.build()) + } + + fn add_federated_root_nodes(&mut self) -> Result<(), FederationError> { + let mut root_kinds = IndexSet::new(); + for (source, root_kinds_to_nodes) in &self.base.query_graph.root_kinds_to_nodes_by_source { + if *source == self.base.query_graph.current_source { + continue; + } + for root_kind in root_kinds_to_nodes.keys() { + root_kinds.insert(*root_kind); + } + } + for root_kind in root_kinds { + self.base.create_root_node(root_kind.into(), root_kind)?; + } + Ok(()) + } + + fn copy_types_to_nodes(&mut self) -> Result<(), FederationError> { + let mut federated_type_to_nodes = IndexMap::new(); + for (source, types_to_nodes) in &self.base.query_graph.types_to_nodes_by_source { + if *source == self.base.query_graph.current_source { + continue; + } + for (type_name, nodes) in types_to_nodes { + let federated_nodes = federated_type_to_nodes + .entry(type_name.clone()) + .or_insert_with(IndexSet::new); + for node in nodes { + federated_nodes.insert(*node); + } + } + } + *self.base.query_graph.types_to_nodes_mut()? = federated_type_to_nodes; + Ok(()) + } + + /// Add the edges from supergraph roots to the subgraph ones. Also, for each root kind, we also + /// add edges from the corresponding root type of each subgraph to the root type of other + /// subgraphs (and for @defer, like for @key, we also add self-node loops). This encodes the + /// fact that if a field returns a root type, we can always query any subgraph from that point. + fn add_root_edges(&mut self) -> Result<(), FederationError> { + let mut new_edges = Vec::new(); + for (source, root_kinds_to_nodes) in &self.base.query_graph.root_kinds_to_nodes_by_source { + if *source == self.base.query_graph.current_source { + continue; + } + for (root_kind, root_node) in root_kinds_to_nodes { + let federated_root_node = self + .base + .query_graph + .root_kinds_to_nodes()? + .get(root_kind) + .ok_or_else(|| SingleFederationError::Internal { + message: "Federated root node unexpectedly missing".to_owned(), + })?; + new_edges.push(QueryGraphEdgeData { + head: *federated_root_node, + tail: *root_node, + transition: QueryGraphEdgeTransition::SubgraphEnteringTransition, + conditions: None, + }); + for (other_source, other_root_kinds_to_nodes) in + &self.base.query_graph.root_kinds_to_nodes_by_source + { + if *other_source == self.base.query_graph.current_source { + continue; + } + if let Some(other_root_node) = other_root_kinds_to_nodes.get(root_kind) { + new_edges.push(QueryGraphEdgeData { + head: *root_node, + tail: *other_root_node, + transition: QueryGraphEdgeTransition::RootTypeResolution { + root_kind: *root_kind, + }, + conditions: None, + }) + } + } + } + } + for new_edge in new_edges { + new_edge.add_to(&mut self.base)?; + } + Ok(()) + } + + /// Handle @key by adding the appropriate key-resolution edges. + fn handle_key(&mut self) -> Result<(), FederationError> { + // We'll look at adding edges from "other subgraphs" to the current type. So the tail of + // all the edges we'll build here is always going to be the same. + for tail in self.base.query_graph.graph.node_indices() { + let mut new_edges = Vec::new(); + let tail_weight = self.base.query_graph.node_weight(tail)?; + let source = &tail_weight.source; + if *source == self.base.query_graph.current_source { + continue; + } + // Ignore federated root nodes. + let QueryGraphNodeType::SchemaType(type_pos) = &tail_weight.type_ else { + continue; + }; + let schema = self.base.query_graph.schema_by_source(source)?; + let directives = schema + .schema() + .types + .get(type_pos.type_name()) + .ok_or_else(|| SingleFederationError::Internal { + message: format!( + "Type \"{}\" unexpectedly missing from subgraph \"{}\"", + type_pos, source, + ), + })? + .directives(); + let subgraph_data = self.subgraphs.get(source)?; + let is_interface_object = matches!(type_pos, OutputTypeDefinitionPosition::Object(_)) + && directives.has(&subgraph_data.interface_object_directive_definition_name); + + for application in resolvable_key_applications( + directives, + &subgraph_data.key_directive_definition_name, + subgraph_data.federation_spec_definition, + )? { + // The @key directive creates an edge from every subgraph having that type to + // the current subgraph. In other words, the fact this subgraph has a @key means + // that the current subgraph can be queried for the entity (through _entities) + // as long as "the other side" can provide the proper field values. Note that we + // only require that "the other side" can gather the key fields (through + // the path conditions; note that it's possible those conditions are never + // satisfiable), but we don't care that it defines the same key, because it's + // not a technical requirement (and in general while we probably don't want to + // allow a type to be an entity in some subgraphs but not others, this is not + // the place to impose that restriction, and this may be at least temporarily + // useful to allow convert a type to an entity). + let Ok(type_pos): Result = + type_pos.clone().try_into() + else { + return Err(SingleFederationError::Internal { + message: format!( + "Invalid \"@key\" application on non-object/interface type \"{}\" in subgraph \"{}\"", + type_pos, + source, + ) + }.into()); + }; + let conditions = Arc::new(parse_field_set( + schema, + type_pos.type_name().clone(), + &application.fields, + )?); + + // Note that each subgraph has a key edge to itself (when head == tail below). + // We usually ignore these edges, but they exist for the special case of @defer, + // where we technically may have to take such "edges to self" as meaning to + // "re-enter" a subgraph for a deferred section. + for (other_source, other_types_to_nodes) in + &self.base.query_graph.types_to_nodes_by_source + { + if *other_source == self.base.query_graph.current_source { + continue; + } + + let other_nodes = other_types_to_nodes.get(type_pos.type_name()); + if let Some(other_nodes) = other_nodes { + let head = other_nodes.first().ok_or_else(|| { + SingleFederationError::Internal { + message: format!( + "Types-to-nodes set unexpectedly empty for type \"{}\" in subgraph \"{}\"", + type_pos, + other_source, + ), + } + })?; + // Note that later, when we've handled @provides, this might not be true + // anymore as @provides may create copy of a certain type. But for now, it's + // true. + if other_nodes.len() > 1 { + return Err( + SingleFederationError::Internal { + message: format!( + "Types-to-nodes set unexpectedly had more than one element for type \"{}\" in subgraph \"{}\"", + type_pos, + other_source, + ), + } + .into() + ); + } + // The edge goes from the other subgraph to this one. + new_edges.push(QueryGraphEdgeData { + head: *head, + tail, + transition: QueryGraphEdgeTransition::KeyResolution, + conditions: Some(conditions.clone()), + }) + } + + // Additionally, if the key is on an @interfaceObject and this "other" subgraph + // has some of the implementations of the corresponding interface, then we need + // an edge from each of those implementations (to the @interfaceObject). This is + // used when an entity of a specific implementation is queried first, but then + // some of the requested fields are only provided by that @interfaceObject. + if is_interface_object { + let type_in_supergraph_pos = self + .supergraph_schema + .get_type(type_pos.type_name().clone())?; + let TypeDefinitionPosition::Interface(type_in_supergraph_pos) = + type_in_supergraph_pos + else { + return Err(SingleFederationError::Internal { + message: format!( + "Type \"{}\" was marked with \"@interfaceObject\" in subgraph \"{}\", but was non-interface in supergraph", + type_pos, + other_source, + ) + }.into()); + }; + for implementation_type_in_supergraph_pos in self + .supergraph_schema + .possible_runtime_types(type_in_supergraph_pos.into())? + { + // That implementation type may or may not exists in the "other + // subgraph". If it doesn't, we just have nothing to do for that + // particular implementation. If it does, we'll add the proper edge, but + // note that we're guaranteed to have at most one node for the same + // reasons as mentioned above (only the handling of @provides will make + // it so that there can be more than one node per type). + let Some(implementation_nodes) = self + .base + .query_graph + .types_to_nodes_by_source(other_source)? + .get(&implementation_type_in_supergraph_pos.type_name) + else { + continue; + }; + let head = implementation_nodes.first().ok_or_else(|| { + SingleFederationError::Internal { + message: format!( + "Types-to-nodes set unexpectedly empty for type \"{}\" in subgraph \"{}\"", + implementation_type_in_supergraph_pos, + other_source, + ), + } + })?; + if implementation_nodes.len() > 1 { + return Err( + SingleFederationError::Internal { + message: format!( + "Types-to-nodes set unexpectedly had more than one element for type \"{}\" in subgraph \"{}\"", + implementation_type_in_supergraph_pos, + other_source, + ), + }.into() + ); + } + + // The key goes from the implementation type in the "other subgraph" to + // the @interfaceObject type in this one, so the `conditions` will be + // "fetched" on the implementation type, but the `conditions` have been + // parsed on the @interfaceObject type. We'd like it to use fields from + // the implementation type and not the @interfaceObject type, so we + // re-parse the condition using the implementation type. This could + // fail, but in that case it just means that key is not usable in the + // other subgraph. + let other_schema = + self.base.query_graph.schema_by_source(other_source)?; + let implementation_type_in_other_subgraph_pos: CompositeTypeDefinitionPosition = + other_schema.get_type(implementation_type_in_supergraph_pos.type_name.clone())?.try_into()?; + let Ok(implementation_conditions) = parse_field_set( + other_schema, + implementation_type_in_other_subgraph_pos + .type_name() + .clone(), + &application.fields, + ) else { + // Ignored on purpose: it just means the key is not usable on this + // subgraph. + continue; + }; + new_edges.push(QueryGraphEdgeData { + head: *head, + tail, + transition: QueryGraphEdgeTransition::KeyResolution, + conditions: Some(Arc::new(implementation_conditions)), + }) + } + } + } + } + for new_edge in new_edges { + new_edge.add_to(&mut self.base)?; + } + } + Ok(()) + } + + /// Handle @requires by updating the appropriate field-collecting edges. + fn handle_requires(&mut self) -> Result<(), FederationError> { + // We'll look at any field-collecting edges with @requires and adding their conditions to + // those edges. + for edge in self.base.query_graph.graph.edge_indices() { + let edge_weight = self.base.query_graph.edge_weight(edge)?; + let QueryGraphEdgeTransition::FieldCollection { + source, + field_definition_position, + .. + } = &edge_weight.transition + else { + continue; + }; + if *source == self.base.query_graph.current_source { + continue; + } + // Nothing prior to this should have set any conditions for field-collecting edges. + // This won't be the case after this method though. + if edge_weight.conditions.is_some() { + return Err(SingleFederationError::Internal { + message: format!( + "Field-collection edge for field \"{}\" unexpectedly had conditions", + field_definition_position, + ), + } + .into()); + } + let schema = self.base.query_graph.schema_by_source(source)?; + let subgraph_data = self.subgraphs.get(source)?; + let field = field_definition_position.get(schema.schema())?; + let mut all_conditions = Vec::new(); + for directive in field + .directives + .get_all(&subgraph_data.requires_directive_definition_name) + { + let application = subgraph_data + .federation_spec_definition + .requires_directive_arguments(directive)?; + let conditions = parse_field_set( + schema, + field_definition_position.parent().type_name().clone(), + &application.fields, + )?; + all_conditions.push(conditions); + } + if all_conditions.is_empty() { + continue; + } + // PORT_NOTE: I'm not sure when this list will ever not be a singleton list, but it's + // like this way in the JS codebase, so we'll mimic the behavior for now. + // + // TODO: This is an optimization to avoid unnecessary inter-conversion between + // the apollo-rs operation representation and the federation-next one. This wasn't a + // problem in the JS codebase, as it would use its own operation representation from + // the start. Eventually when operation processing code is ready and we make the switch + // to using the federation-next representation everywhere, we can probably simplify + // this. + let new_conditions = if all_conditions.len() == 1 { + all_conditions + .pop() + .ok_or_else(|| SingleFederationError::Internal { + message: "Singleton list was unexpectedly empty".to_owned(), + })? + } else { + merge_selection_sets(all_conditions)? + }; + let edge_weight_mut = self.base.query_graph.edge_weight_mut(edge)?; + edge_weight_mut.conditions = Some(Arc::new(new_conditions)); + } + Ok(()) + } + + /// Handle @provides by copying the appropriate nodes/edges. + fn handle_provides(&mut self) -> Result<(), FederationError> { + let mut provide_id = 0; + for edge in self.base.query_graph.graph.edge_indices() { + let edge_weight = self.base.query_graph.edge_weight(edge)?; + let QueryGraphEdgeTransition::FieldCollection { + source, + field_definition_position, + .. + } = &edge_weight.transition + else { + continue; + }; + if *source == self.base.query_graph.current_source { + continue; + } + let source = source.clone(); + let schema = self.base.query_graph.schema_by_source(&source)?; + let subgraph_data = self.subgraphs.get(&source)?; + let field = field_definition_position.get(schema.schema())?; + let field_type_pos = schema.get_type(field.ty.inner_named_type().clone())?; + let mut all_conditions = Vec::new(); + for directive in field + .directives + .get_all(&subgraph_data.provides_directive_definition_name) + { + let application = subgraph_data + .federation_spec_definition + .provides_directive_arguments(directive)?; + let field_type_pos: CompositeTypeDefinitionPosition = + field_type_pos.clone().try_into()?; + let conditions = parse_field_set( + schema, + field_type_pos.type_name().clone(), + &application.fields, + )?; + all_conditions.push(conditions); + } + if all_conditions.is_empty() { + continue; + } + provide_id += 1; + // PORT_NOTE: I'm not sure when this list will ever not be a singleton list, but it's + // like this way in the JS codebase, so we'll mimic the behavior for now. + // + // Note that the JS codebase had a bug here when there was more than one selection set, + // where it would copy per @provides application, but only point the field-collecting + // edge toward the last copy. We do something similar to @requires instead, where we + // merge the selection sets before into one. + // + // TODO: This is an optimization to avoid unnecessary inter-conversion between + // the apollo-rs operation representation and the federation-next one. This wasn't a + // problem in the JS codebase, as it would use its own operation representation from + // the start. Eventually when operation processing code is ready and we make the switch + // to using the federation-next representation everywhere, we can probably simplify + // this. + let new_conditions = if all_conditions.len() == 1 { + all_conditions + .pop() + .ok_or_else(|| SingleFederationError::Internal { + message: "Singleton list was unexpectedly empty".to_owned(), + })? + } else { + merge_selection_sets(all_conditions)? + }; + // We make a copy of the tail node (representing the field's type) with all the same + // out-edges, and we change this particular in-edge to point to the new copy. We then + // add all the provides edges starting from the copy. + let (_, tail) = self.base.query_graph.edge_endpoints(edge)?; + let new_tail = Self::copy_for_provides(&mut self.base, tail, provide_id)?; + Self::update_edge_tail(&mut self.base, edge, new_tail)?; + Self::add_provides_edges( + &mut self.base, + &source, + new_tail, + &new_conditions, + provide_id, + )?; + } + Ok(()) + } + + fn add_provides_edges( + base: &mut BaseQueryGraphBuilder, + source: &NodeStr, + head: NodeIndex, + provided: &SelectionSet, + provide_id: u32, + ) -> Result<(), FederationError> { + let mut stack = vec![(head, provided)]; + while let Some((node, selection_set)) = stack.pop() { + // We reverse-iterate through the selections to cancel out the reversing that the stack + // does. + for selection in selection_set.selections.values().rev() { + match selection { + Selection::Field(field_selection) => { + let existing_edge_info = base + .query_graph + .graph + .edges_directed(node, Direction::Outgoing) + .find_map(|edge_ref| { + let edge_weight = edge_ref.weight(); + let QueryGraphEdgeTransition::FieldCollection { + field_definition_position, + .. + } = &edge_weight.transition + else { + return None; + }; + if field_definition_position.field_name() + == field_selection.field.data().name() + { + Some((edge_ref.id(), edge_ref.target())) + } else { + None + } + }); + if let Some((edge, tail)) = existing_edge_info { + // If this is a leaf field, then we don't really have anything to do. + // Otherwise, we need to copy the tail and continue propagating the + // provided selections from there. + if let Some(selections) = &field_selection.selection_set { + let new_tail = Self::copy_for_provides(base, tail, provide_id)?; + Self::update_edge_tail(base, edge, new_tail)?; + stack.push((new_tail, selections)) + } + } else { + // There are no existing edges, which means that it's an edge added by + // an @provides to an @external field. We find the existing node it + // leads to. + // + // PORT_NOTE: The JS codebase would create the node if it didn't exist, + // but this is a holdover from when subgraph query graph building + // would skip @external fields completely instead of just recursing + // into the type. + // + // Also, the JS codebase appears to have a bug where they find some node + // with the appropriate source and type name, but they don't guarantee + // that the node has no provide_id (i.e. it may be a copied node), which + // means they may have extra edges that accordingly can't be taken. We + // fix this below by filtering by provide_id. + let field = field_selection + .field + .data() + .field_position + .get(field_selection.field.data().schema.schema())?; + let tail_type = field.ty.inner_named_type(); + let possible_tails = base + .query_graph + .types_to_nodes_by_source(source)? + .get(tail_type) + .ok_or_else(|| SingleFederationError::Internal { + message: format!( + "Types-to-nodes map missing type \"{}\" in subgraph \"{}\"", + tail_type, source, + ), + })?; + // Note because this is an IndexSet, the non-provides should be first + // since it was added first, but it's a bit fragile so we fallback to + // checking the whole set if it's not first. + let mut tail: Option = None; + for possible_tail in possible_tails { + let possible_tail_weight = + base.query_graph.node_weight(*possible_tail)?; + if possible_tail_weight.provide_id.is_none() { + tail = Some(*possible_tail); + break; + } + } + let Some(tail) = tail else { + return Err( + SingleFederationError::Internal { + message: format!( + "Missing non-provides node for type \"{}\" in subgraph \"{}\"", + tail_type, + source, + ) + }.into() + ); + }; + // If this is a leaf field, then just create the new edge and we're + // done. Otherwise, we should copy the node, add the edge, and continue + // propagating. + let transition = QueryGraphEdgeTransition::FieldCollection { + source: source.clone(), + field_definition_position: field_selection + .field + .data() + .field_position + .clone(), + is_part_of_provides: true, + }; + if let Some(selections) = &field_selection.selection_set { + let new_tail = Self::copy_for_provides(base, tail, provide_id)?; + base.add_edge(node, new_tail, transition, None)?; + stack.push((new_tail, selections)) + } else { + base.add_edge(node, tail, transition, None)?; + } + } + } + Selection::InlineFragment(inline_fragment_selection) => { + if let Some(type_condition_pos) = &inline_fragment_selection + .inline_fragment + .data() + .type_condition_position + { + // We should always have an edge: otherwise it would mean we list a type + // condition for a type that isn't in the subgraph, but the @provides + // shouldn't have validated in the first place (another way to put this + // is, contrary to fields, there is no way currently to mark a full type + // as @external). + // + // TODO: Query graph creation during composition only creates Downcast + // edges between abstract types and their possible runtime object types, + // so the requirement above effectively prohibits the use of type + // conditions containing abstract types in @provides. This is fine for + // query planning since we have the same Downcast edges (additionally + // for query planning, there are usually edges between abstract types + // when their intersection is at least size 2). However, when this code + // gets used in composition, the assertion below will be the error users + // will see in those cases, and it would be better to do a formal check + // with better messaging during merging instead of during query graph + // construction. + let (edge, tail) = base + .query_graph + .graph + .edges_directed(node, Direction::Outgoing) + .find_map(|edge_ref| { + let edge_weight = edge_ref.weight(); + let QueryGraphEdgeTransition::Downcast { + to_type_position, .. + } = &edge_weight.transition + else { + return None; + }; + if to_type_position == type_condition_pos { + Some((edge_ref.id(), edge_ref.target())) + } else { + None + } + }) + .ok_or_else(|| { + SingleFederationError::Internal { + message: format!( + "Shouldn't have selection \"{}\" in an @provides, as its type condition has no query graph edge", + inline_fragment_selection, + ) + } + })?; + let new_tail = Self::copy_for_provides(base, tail, provide_id)?; + Self::update_edge_tail(base, edge, new_tail)?; + stack.push((new_tail, &inline_fragment_selection.selection_set)) + } else { + // Essentially ignore the condition in this case, and continue + // propagating the provided selections. + stack.push((node, &inline_fragment_selection.selection_set)); + } + } + Selection::FragmentSpread(_) => { + return Err(SingleFederationError::Internal { + message: "Unexpectedly found named fragment in FieldSet scalar" + .to_owned(), + } + .into()); + } + } + } + } + Ok(()) + } + + fn update_edge_tail( + base: &mut BaseQueryGraphBuilder, + edge: EdgeIndex, + tail: NodeIndex, + ) -> Result<(), FederationError> { + // Note that petgraph has no method to directly update an edge's endpoints. Instead, you + // must: + // 1. Add a new edge, with the same weight but updated endpoints. This edge then becomes the + // last edge in the graph (i.e. the one with highest edge index). + // 2. Remove the old edge. As per the API docs, this causes the last edge's index to change + // to be the one that was removed. + // This results in a Graph where it looks like only the edge's endpoints have changed while + // its index and weight are unchanged, but really its weight has been cloned (which is + // cheap). + let (new_edge_head, _) = base.query_graph.edge_endpoints(edge)?; + let new_edge_weight = base.query_graph.edge_weight(edge)?.clone(); + base.query_graph + .graph + .add_edge(new_edge_head, tail, new_edge_weight); + base.query_graph.graph.remove_edge(edge); + Ok(()) + } + + /// Copies the given node and its outgoing edges, returning the new node's index. + fn copy_for_provides( + base: &mut BaseQueryGraphBuilder, + node: NodeIndex, + provide_id: u32, + ) -> Result { + let current_source = base.query_graph.current_source.clone(); + let result = Self::copy_for_provides_internal(base, node, provide_id); + // Ensure that the current source resets, even if the copy unexpectedly fails. + base.query_graph.current_source = current_source; + let (new_node, type_pos) = result?; + base.query_graph + .types_to_nodes_mut()? + .get_mut(type_pos.type_name()) + .ok_or_else(|| SingleFederationError::Internal { + message: format!( + "Unexpectedly missing @provides type \"{}\" in types-to-nodes map", + type_pos, + ), + })? + .insert(new_node); + Ok(new_node) + } + + fn copy_for_provides_internal( + base: &mut BaseQueryGraphBuilder, + node: NodeIndex, + provide_id: u32, + ) -> Result<(NodeIndex, OutputTypeDefinitionPosition), FederationError> { + let node_weight = base.query_graph.node_weight(node)?; + let QueryGraphNodeType::SchemaType(type_pos) = node_weight.type_.clone() else { + return Err(SingleFederationError::Internal { + message: "Unexpectedly found @provides for federated root node".to_owned(), + } + .into()); + }; + let has_reachable_cross_subgraph_edges = node_weight.has_reachable_cross_subgraph_edges; + base.query_graph.current_source = node_weight.source.clone(); + let new_node = base.create_new_node(type_pos.clone().into())?; + let new_node_weight = base.query_graph.node_weight_mut(new_node)?; + new_node_weight.provide_id = Some(provide_id); + new_node_weight.has_reachable_cross_subgraph_edges = has_reachable_cross_subgraph_edges; + + let mut new_edges = Vec::new(); + for edge_ref in base + .query_graph + .graph + .edges_directed(node, Direction::Outgoing) + { + let edge_tail = edge_ref.target(); + let edge_weight = edge_ref.weight(); + new_edges.push(QueryGraphEdgeData { + head: new_node, + tail: edge_tail, + transition: edge_weight.transition.clone(), + conditions: edge_weight.conditions.clone(), + }); + } + for new_edge in new_edges { + new_edge.add_to(base)?; + } + Ok((new_node, type_pos)) + } + + /// Handle @interfaceObject by adding the appropriate fake-downcast self-edges. + fn handle_interface_object(&mut self) -> Result<(), FederationError> { + // There are cases where only an/some implementation(s) of an interface are queried, and + // that could apply to an interface that is an @interfaceObject in some subgraph. Consider + // the following example: + // ```graphql + // type Query { + // getIs: [I] + // } + // + // type I @key(fields: "id") @interfaceObject { + // id: ID! + // x: Int + // } + // ``` + // where we suppose that `I` has some implementations `A`, `B`, and `C` in some other subgraph. + // Now, consider query: + // ```graphql + // { + // getIs { + // ... on B { + // x + // } + // } + // } + // ``` + // So here, we query `x` (which the subgraph provides) but we only do so for one of the + // implementations. So in that case, we essentially need to figure out the `__typename` first + // (or more precisely, we need to know the real __typename "eventually"; we could theoretically + // query `x` first, and then get the __typename to know if we should keep the result or discard + // it, and that could be more efficient in certain cases, but as we don't know both 1) if `x` + // is expansive to resolve and 2) what ratio of the results from `getIs` will be `B` versus some + // other implementation, it is "safer" to get the __typename first and only resolve `x` when we + // need to). + // + // Long story short, to solve this, we create edges from @interfaceObject types to themselves + // for every implementation type of the interface: those edges will be taken when we try to take + // a `... on B` condition, and those edges have __typename have a condition, forcing us to find + // __typename in another subgraph first. + let mut new_edges = Vec::new(); + for (source, schema) in &self.base.query_graph.sources { + if *source == self.base.query_graph.current_source { + continue; + } + let subgraph_data = self.subgraphs.get(source)?; + for type_pos in &schema + .referencers() + .get_directive(&subgraph_data.interface_object_directive_definition_name)? + .object_types + { + // This method is run after handling @provides, so there may be multiple nodes here + // instead of just one. We specifically just want to add the self-edges to the + // non-provides node. This is because any @provides in a subgraph whose field set + // contains an @interfaceObject object type couldn't actually place a type condition + // on that object type, since that field set must validate against the subgraph + // schema (and object types have no intersection with other object types, even if + // the subgraph had any implementation types). This is also why we run this method + // after the @provides handling instead of before (since we don't want to clone + // the self-edges added by this method). + let possible_nodes = self + .base + .query_graph + .types_to_nodes_by_source(source)? + .get(&type_pos.type_name) + .ok_or_else(|| SingleFederationError::Internal { + message: format!( + "Types-to-nodes map missing type \"{}\" in subgraph \"{}\"", + type_pos, source, + ), + })?; + // Note because this is an IndexSet, the non-provides should be first since it was + // added first, but it's a bit fragile so we fallback to checking the whole set if + // it's not first. + let mut node: Option = None; + for possible_node in possible_nodes { + let possible_tail_weight = self.base.query_graph.node_weight(*possible_node)?; + if possible_tail_weight.provide_id.is_none() { + node = Some(*possible_node); + break; + } + } + let Some(node) = node else { + return Err(SingleFederationError::Internal { + message: format!( + "Missing non-provides node for type \"{}\" in subgraph \"{}\"", + type_pos, source, + ), + } + .into()); + }; + let type_in_supergraph_pos = self + .supergraph_schema + .get_type(type_pos.type_name.clone())?; + let TypeDefinitionPosition::Interface(type_in_supergraph_pos) = + type_in_supergraph_pos + else { + return Err(SingleFederationError::Internal { + message: format!( + "Type \"{}\" was marked with \"@interfaceObject\" in subgraph \"{}\", but was non-interface in supergraph", + type_pos, + source, + ) + }.into()); + }; + let conditions = Arc::new(parse_field_set( + schema, + type_in_supergraph_pos.type_name.clone(), + "__typename", + )?); + for implementation_type_in_supergraph_pos in self + .supergraph_schema + .possible_runtime_types(type_in_supergraph_pos.into())? + { + let transition = QueryGraphEdgeTransition::InterfaceObjectFakeDownCast { + source: source.clone(), + from_type_position: type_pos.clone().into(), + to_type_name: implementation_type_in_supergraph_pos.type_name, + }; + new_edges.push(QueryGraphEdgeData { + head: node, + tail: node, + transition, + conditions: Some(conditions.clone()), + }); + } + } + } + for new_edge in new_edges { + new_edge.add_to(&mut self.base)?; + } + Ok(()) + } + + /// Precompute which followup edges for a given edge are non-trivial. + fn precompute_non_trivial_followup_edges(&mut self) -> Result<(), FederationError> { + for edge in self.base.query_graph.graph.edge_indices() { + let edge_weight = self.base.query_graph.edge_weight(edge)?; + let (_, tail) = self.base.query_graph.edge_endpoints(edge)?; + let mut non_trivial_followups = IndexSet::new(); + for followup_edge_ref in self + .base + .query_graph + .graph + .edges_directed(tail, Direction::Outgoing) + { + let followup_edge_weight = followup_edge_ref.weight(); + match edge_weight.transition { + QueryGraphEdgeTransition::KeyResolution => { + // After taking a key from subgraph A to B, there is no point of following + // that up with another key to subgraph C if that key has the same + // conditions. This is because, due to the way key edges are created, if we + // have a key (with some conditions X) from B to C, then we are guaranteed + // to also have a key (with the same conditions X) from A to C, and so it's + // that later key we should be using in the first place. In other words, + // it's never better to do 2 hops rather than 1. + if matches!( + followup_edge_weight.transition, + QueryGraphEdgeTransition::KeyResolution + ) { + let Some(conditions) = &edge_weight.conditions else { + return Err(SingleFederationError::Internal { + message: "Key resolution edge unexpectedly missing conditions" + .to_owned(), + } + .into()); + }; + let Some(followup_conditions) = &followup_edge_weight.conditions else { + return Err(SingleFederationError::Internal { + message: "Key resolution edge unexpectedly missing conditions" + .to_owned(), + } + .into()); + }; + if conditions.selections == followup_conditions.selections { + continue; + } + } + } + QueryGraphEdgeTransition::RootTypeResolution { .. } => { + // A 'RootTypeResolution' means that a query reached the query type (or + // another root type) in some subgraph A and we're looking at jumping to + // another subgraph B. But like for keys, there is no point in trying to + // jump directly to yet another subpraph C from B, since we can always jump + // directly from A to C and it's better. + if matches!( + followup_edge_weight.transition, + QueryGraphEdgeTransition::RootTypeResolution { .. } + ) { + continue; + } + } + QueryGraphEdgeTransition::SubgraphEnteringTransition => { + // This is somewhat similar to 'RootTypeResolution' except that we're + // starting the query. Still, we shouldn't do "start of query" -> B -> C, + // since we can do "start of query" -> C and that's always better. + if matches!( + followup_edge_weight.transition, + QueryGraphEdgeTransition::SubgraphEnteringTransition + ) { + continue; + } + } + _ => {} + } + non_trivial_followups.insert(followup_edge_ref.id()); + } + self.base + .query_graph + .non_trivial_followup_edges + .insert(edge, non_trivial_followups); + } + Ok(()) + } +} + +const FEDERATED_GRAPH_ROOT_SOURCE: &str = "_"; + +struct FederatedQueryGraphBuilderSubgraphs { + map: IndexMap, +} + +impl FederatedQueryGraphBuilderSubgraphs { + fn new(base: &BaseQueryGraphBuilder) -> Result { + let mut subgraphs = FederatedQueryGraphBuilderSubgraphs { + map: IndexMap::new(), + }; + for (source, schema) in &base.query_graph.sources { + if *source == base.query_graph.current_source { + continue; + } + let federation_spec_definition = get_federation_spec_definition_from_subgraph(schema)?; + let key_directive_definition_name = federation_spec_definition + .key_directive_definition(schema)? + .name + .clone(); + let requires_directive_definition_name = federation_spec_definition + .requires_directive_definition(schema)? + .name + .clone(); + let provides_directive_definition_name = federation_spec_definition + .provides_directive_definition(schema)? + .name + .clone(); + let interface_object_directive_definition_name = federation_spec_definition + .interface_object_directive_definition(schema)? + .map(|d| d.name.clone()) + .ok_or_else(|| { + // Extracted subgraphs should use the latest federation spec version, which + // means they should include an @interfaceObject definition. + SingleFederationError::Internal { + message: format!( + "Subgraph \"{}\" unexpectedly missing @interfaceObject definition", + source, + ), + } + })?; + subgraphs.map.insert( + source.clone(), + FederatedQueryGraphBuilderSubgraphData { + federation_spec_definition, + key_directive_definition_name, + requires_directive_definition_name, + provides_directive_definition_name, + interface_object_directive_definition_name, + }, + ); + } + Ok(subgraphs) + } + + fn get( + &self, + source: &str, + ) -> Result<&FederatedQueryGraphBuilderSubgraphData, FederationError> { + self.map.get(source).ok_or_else(|| { + SingleFederationError::Internal { + message: "Subgraph data unexpectedly missing".to_owned(), + } + .into() + }) + } +} + +struct FederatedQueryGraphBuilderSubgraphData { + federation_spec_definition: &'static FederationSpecDefinition, + key_directive_definition_name: Name, + requires_directive_definition_name: Name, + provides_directive_definition_name: Name, + interface_object_directive_definition_name: Name, +} + +#[derive(Debug)] +struct QueryGraphEdgeData { + head: NodeIndex, + tail: NodeIndex, + transition: QueryGraphEdgeTransition, + conditions: Option>, +} + +impl QueryGraphEdgeData { + fn add_to(self, builder: &mut BaseQueryGraphBuilder) -> Result<(), FederationError> { + builder.add_edge(self.head, self.tail, self.transition, self.conditions) + } +} + +fn resolvable_key_applications( + directives: &ComponentDirectiveList, + key_directive_definition_name: &Name, + federation_spec_definition: &'static FederationSpecDefinition, +) -> Result, FederationError> { + let mut applications = Vec::new(); + for directive in directives.get_all(key_directive_definition_name) { + let key_directive_application = + federation_spec_definition.key_directive_arguments(directive)?; + if !key_directive_application.resolvable { + continue; + } + applications.push(key_directive_application); + } + Ok(applications) +} + +#[cfg(test)] +mod tests { + use apollo_compiler::name; + use apollo_compiler::schema::Name; + use apollo_compiler::NodeStr; + use apollo_compiler::Schema; + use indexmap::IndexMap; + use indexmap::IndexSet; + use petgraph::graph::NodeIndex; + use petgraph::visit::EdgeRef; + use petgraph::Direction; + + use crate::error::FederationError; + use crate::query_graph::build_query_graph::build_query_graph; + use crate::query_graph::QueryGraph; + use crate::query_graph::QueryGraphEdgeTransition; + use crate::query_graph::QueryGraphNode; + use crate::query_graph::QueryGraphNodeType; + use crate::schema::position::ObjectOrInterfaceTypeDefinitionPosition; + use crate::schema::position::ObjectTypeDefinitionPosition; + use crate::schema::position::OutputTypeDefinitionPosition; + use crate::schema::position::ScalarTypeDefinitionPosition; + use crate::schema::position::SchemaRootDefinitionKind; + use crate::schema::ValidFederationSchema; + + const SCHEMA_NAME: NodeStr = NodeStr::from_static(&"test"); + + fn test_query_graph_from_schema_sdl(sdl: &str) -> Result { + let schema = + ValidFederationSchema::new(Schema::parse_and_validate(sdl, "schema.graphql")?)?; + build_query_graph(SCHEMA_NAME, schema) + } + + fn assert_node_type( + query_graph: &QueryGraph, + node: NodeIndex, + output_type_definition_position: OutputTypeDefinitionPosition, + root_kind: Option, + ) -> Result<(), FederationError> { + assert_eq!( + *query_graph.node_weight(node)?, + QueryGraphNode { + type_: QueryGraphNodeType::SchemaType(output_type_definition_position), + source: SCHEMA_NAME, + has_reachable_cross_subgraph_edges: false, + provide_id: None, + root_kind, + }, + ); + Ok(()) + } + + fn named_edges( + query_graph: &QueryGraph, + head: NodeIndex, + field_names: IndexSet, + ) -> Result, FederationError> { + let mut result = IndexMap::new(); + for field_name in field_names { + // PORT_NOTE: In the JS codebase, there were a lot of asserts here, but they were all + // duplicated with single_edge() (or they tested the JS codebase's graph representation, + // which we don't need to do since we're using petgraph), so they're all removed here. + let tail = single_edge(query_graph, head, field_name.clone())?; + result.insert(field_name, tail); + } + Ok(result) + } + + fn single_edge( + query_graph: &QueryGraph, + head: NodeIndex, + field_name: Name, + ) -> Result { + let head_weight = query_graph.node_weight(head)?; + let QueryGraphNodeType::SchemaType(type_pos) = &head_weight.type_ else { + panic!("Unexpectedly found federated root type"); + }; + let type_pos: ObjectOrInterfaceTypeDefinitionPosition = type_pos.clone().try_into()?; + let field_pos = type_pos.field(field_name); + let schema = query_graph.schema()?; + field_pos.get(schema.schema())?; + let expected_field_transition = QueryGraphEdgeTransition::FieldCollection { + source: SCHEMA_NAME, + field_definition_position: field_pos.clone().into(), + is_part_of_provides: false, + }; + let mut tails = query_graph + .graph + .edges_directed(head, Direction::Outgoing) + .filter_map(|edge_ref| { + let edge_weight = edge_ref.weight(); + if edge_weight.transition == expected_field_transition { + assert_eq!(edge_weight.conditions, None); + Some(edge_ref.target()) + } else { + None + } + }) + .collect::>(); + assert_eq!(tails.len(), 1); + Ok(tails.pop().unwrap()) + } + + #[test] + fn building_query_graphs_from_schema_handles_object_types() -> Result<(), FederationError> { + let query_graph = test_query_graph_from_schema_sdl( + r#" + type Query { + t1: T1 + } + + type T1 { + f1: Int + f2: String + f3: T2 + } + + type T2 { + t: T1 + } + "#, + )?; + + // We have 3 object types and 2 scalars (Int and String) + assert_eq!(query_graph.graph.node_count(), 5); + assert_eq!( + query_graph + .root_kinds_to_nodes()? + .keys() + .cloned() + .collect::>(), + IndexSet::from([SchemaRootDefinitionKind::Query]) + ); + + let root_node = query_graph + .root_kinds_to_nodes()? + .get(&SchemaRootDefinitionKind::Query) + .unwrap(); + assert_node_type( + &query_graph, + *root_node, + ObjectTypeDefinitionPosition { + type_name: name!("Query"), + } + .into(), + Some(SchemaRootDefinitionKind::Query), + )?; + assert_eq!( + query_graph + .graph + .edges_directed(*root_node, Direction::Outgoing) + .count(), + 2 + ); + let root_fields = named_edges( + &query_graph, + *root_node, + IndexSet::from([name!("__typename"), name!("t1")]), + )?; + + let root_typename_tail = root_fields.get("__typename").unwrap(); + assert_node_type( + &query_graph, + *root_typename_tail, + ScalarTypeDefinitionPosition { + type_name: name!("String"), + } + .into(), + None, + )?; + + let t1_node = root_fields.get("t1").unwrap(); + assert_node_type( + &query_graph, + *t1_node, + ObjectTypeDefinitionPosition { + type_name: name!("T1"), + } + .into(), + None, + )?; + assert_eq!( + query_graph + .graph + .edges_directed(*t1_node, Direction::Outgoing) + .count(), + 4 + ); + let t1_fields = named_edges( + &query_graph, + *t1_node, + IndexSet::from([name!("__typename"), name!("f1"), name!("f2"), name!("f3")]), + )?; + + let t1_typename_tail = t1_fields.get("__typename").unwrap(); + assert_node_type( + &query_graph, + *t1_typename_tail, + ScalarTypeDefinitionPosition { + type_name: name!("String"), + } + .into(), + None, + )?; + + let t1_f1_tail = t1_fields.get("f1").unwrap(); + assert_node_type( + &query_graph, + *t1_f1_tail, + ScalarTypeDefinitionPosition { + type_name: name!("Int"), + } + .into(), + None, + )?; + assert_eq!( + query_graph + .graph + .edges_directed(*t1_f1_tail, Direction::Outgoing) + .count(), + 0 + ); + + let t1_f2_tail = t1_fields.get("f2").unwrap(); + assert_node_type( + &query_graph, + *t1_f2_tail, + ScalarTypeDefinitionPosition { + type_name: name!("String"), + } + .into(), + None, + )?; + assert_eq!( + query_graph + .graph + .edges_directed(*t1_f2_tail, Direction::Outgoing) + .count(), + 0 + ); + + let t2_node = t1_fields.get("f3").unwrap(); + assert_node_type( + &query_graph, + *t2_node, + ObjectTypeDefinitionPosition { + type_name: name!("T2"), + } + .into(), + None, + )?; + assert_eq!( + query_graph + .graph + .edges_directed(*t2_node, Direction::Outgoing) + .count(), + 2 + ); + let t2_fields = named_edges( + &query_graph, + *t2_node, + IndexSet::from([name!("__typename"), name!("t")]), + )?; + + let t2_typename_tail = t2_fields.get("__typename").unwrap(); + assert_node_type( + &query_graph, + *t2_typename_tail, + ScalarTypeDefinitionPosition { + type_name: name!("String"), + } + .into(), + None, + )?; + + let t2_t_tail = t2_fields.get("t").unwrap(); + assert_node_type( + &query_graph, + *t2_t_tail, + ObjectTypeDefinitionPosition { + type_name: name!("T1"), + } + .into(), + None, + )?; + + Ok(()) + } +} diff --git a/apollo-federation/src/query_graph/condition_resolver.rs b/apollo-federation/src/query_graph/condition_resolver.rs new file mode 100644 index 0000000000..44d380f0b7 --- /dev/null +++ b/apollo-federation/src/query_graph/condition_resolver.rs @@ -0,0 +1,195 @@ +// PORT_NOTE: Unlike in JS version, `QueryPlanningTraversal` does not have a +// `CachingConditionResolver` as a field, but instead implements the `ConditionResolver` +// trait directly using `ConditionResolverCache`. +use std::sync::Arc; + +use indexmap::IndexMap; +use petgraph::graph::EdgeIndex; + +use crate::error::FederationError; +use crate::query_graph::graph_path::ExcludedConditions; +use crate::query_graph::graph_path::ExcludedDestinations; +use crate::query_graph::graph_path::OpGraphPathContext; +use crate::query_graph::path_tree::OpPathTree; +use crate::query_plan::QueryPlanCost; + +/// Note that `ConditionResolver`s are guaranteed to be only called for edge with conditions. +pub(crate) trait ConditionResolver { + fn resolve( + &mut self, + edge: EdgeIndex, + context: &OpGraphPathContext, + excluded_destinations: &ExcludedDestinations, + excluded_conditions: &ExcludedConditions, + ) -> Result; +} + +#[derive(Debug, Clone)] +pub(crate) enum ConditionResolution { + Satisfied { + cost: QueryPlanCost, + path_tree: Option>, + }, + Unsatisfied { + reason: Option, + }, +} + +#[derive(Debug, Clone)] +pub(crate) enum UnsatisfiedConditionReason { + NoPostRequireKey, +} + +impl ConditionResolution { + pub(crate) fn no_conditions() -> Self { + Self::Satisfied { + cost: 0, + path_tree: None, + } + } + + pub(crate) fn unsatisfied_conditions() -> Self { + Self::Unsatisfied { reason: None } + } +} + +#[derive(Debug)] +pub(crate) enum ConditionResolutionCacheResult { + /// Cache hit. + Hit(ConditionResolution), + /// Cache miss; can be inserted into cache. + Miss, + /// The value can't be cached; Or, an incompatible value is already in cache. + NotApplicable, +} + +impl ConditionResolutionCacheResult { + pub(crate) fn is_hit(&self) -> bool { + matches!(self, Self::Hit(_)) + } + + pub(crate) fn is_miss(&self) -> bool { + matches!(self, Self::Miss) + } + + pub(crate) fn is_not_applicable(&self) -> bool { + matches!(self, Self::NotApplicable) + } +} + +pub(crate) struct ConditionResolverCache { + // For every edge having a condition, we cache the resolution its conditions when possible. + // We save resolution with the set of excluded edges that were used to compute it: the reason we do this is + // that excluded edges impact the resolution, so we should only used a cached value if we know the excluded + // edges are the same as when caching, and while we could decide to cache only when we have no excluded edges + // at all, this would sub-optimal for types that have multiple keys, as the algorithm will always at least + // include the previous key edges to the excluded edges of other keys. In other words, if we only cached + // when we have no excluded edges, we'd only ever use the cache for the first key of every type. However, + // as the algorithm always try keys in the same order (the order of the edges in the query graph), including + // the excluded edges we see on the first ever call is actually the proper thing to do. + edge_states: IndexMap, +} + +impl ConditionResolverCache { + pub(crate) fn new() -> Self { + Self { + edge_states: Default::default(), + } + } + + pub(crate) fn contains( + &mut self, + edge: EdgeIndex, + context: &OpGraphPathContext, + excluded_destinations: &ExcludedDestinations, + excluded_conditions: &ExcludedConditions, + ) -> ConditionResolutionCacheResult { + // We don't cache if there is a context or excluded conditions because those would impact the resolution and + // we don't want to cache a value per-context and per-excluded-conditions (we also don't cache per-excluded-edges though + // instead we cache a value only for the first-see excluded edges; see above why that work in practice). + // TODO: we could actually have a better handling of the context: it doesn't really change how we'd resolve the condition, it's only + // that the context, if not empty, would have to be added to the trigger of key edges in the resolution path tree when appropriate + // and we currently don't handle that. But we could cache with an empty context, and then apply the proper transformation on the + // cached value `pathTree` when the context is not empty. That said, the context is about active @include/@skip and it's not use + // that commonly, so this is probably not an urgent improvement. + if !context.is_empty() || !excluded_conditions.is_empty() { + return ConditionResolutionCacheResult::NotApplicable; + } + + if let Some((cached_resolution, cached_excluded_destinations)) = self.edge_states.get(&edge) + { + // Cache hit. + // Ensure we have the same excluded destinations as when we cached the value. + if cached_excluded_destinations == excluded_destinations { + return ConditionResolutionCacheResult::Hit(cached_resolution.clone()); + } + // Otherwise, fall back to non-cached computation + ConditionResolutionCacheResult::NotApplicable + } else { + // Cache miss + ConditionResolutionCacheResult::Miss + } + } + + pub(crate) fn insert( + &mut self, + edge: EdgeIndex, + resolution: ConditionResolution, + excluded_destinations: ExcludedDestinations, + ) { + self.edge_states + .insert(edge, (resolution, excluded_destinations)); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::query_graph::graph_path::OpGraphPathContext; + //use crate::link::graphql_definition::{OperationConditional, OperationConditionalKind, BooleanOrVariable}; + + #[test] + fn test_condition_resolver_cache() { + let mut cache = ConditionResolverCache::new(); + + let edge1 = EdgeIndex::new(1); + let empty_context = OpGraphPathContext::default(); + let empty_destinations = ExcludedDestinations::default(); + let empty_conditions = ExcludedConditions::default(); + + assert!(cache + .contains( + edge1, + &empty_context, + &empty_destinations, + &empty_conditions + ) + .is_miss()); + + cache.insert( + edge1, + ConditionResolution::unsatisfied_conditions(), + empty_destinations.clone(), + ); + + assert!(cache + .contains( + edge1, + &empty_context, + &empty_destinations, + &empty_conditions + ) + .is_hit()); + + let edge2 = EdgeIndex::new(2); + + assert!(cache + .contains( + edge2, + &empty_context, + &empty_destinations, + &empty_conditions + ) + .is_miss()); + } +} diff --git a/apollo-federation/src/query_graph/extract_subgraphs_from_supergraph.rs b/apollo-federation/src/query_graph/extract_subgraphs_from_supergraph.rs new file mode 100644 index 0000000000..58ecaf460b --- /dev/null +++ b/apollo-federation/src/query_graph/extract_subgraphs_from_supergraph.rs @@ -0,0 +1,2702 @@ +use std::collections::BTreeMap; +use std::fmt; +use std::fmt::Write; +use std::ops::Deref; + +use apollo_compiler::ast::FieldDefinition; +use apollo_compiler::executable; +use apollo_compiler::name; +use apollo_compiler::schema::Component; +use apollo_compiler::schema::ComponentName; +use apollo_compiler::schema::ComponentOrigin; +use apollo_compiler::schema::DirectiveDefinition; +use apollo_compiler::schema::DirectiveList; +use apollo_compiler::schema::DirectiveLocation; +use apollo_compiler::schema::EnumType; +use apollo_compiler::schema::EnumValueDefinition; +use apollo_compiler::schema::ExtendedType; +use apollo_compiler::schema::ExtensionId; +use apollo_compiler::schema::InputObjectType; +use apollo_compiler::schema::InputValueDefinition; +use apollo_compiler::schema::InterfaceType; +use apollo_compiler::schema::Name; +use apollo_compiler::schema::NamedType; +use apollo_compiler::schema::ObjectType; +use apollo_compiler::schema::ScalarType; +use apollo_compiler::schema::SchemaBuilder; +use apollo_compiler::schema::Type; +use apollo_compiler::schema::UnionType; +use apollo_compiler::validation::Valid; +use apollo_compiler::Node; +use apollo_compiler::NodeStr; +use indexmap::IndexMap; +use indexmap::IndexSet; +use lazy_static::lazy_static; +use time::OffsetDateTime; + +use crate::error::FederationError; +use crate::error::MultipleFederationErrors; +use crate::error::SingleFederationError; +use crate::link::federation_spec_definition::get_federation_spec_definition_from_subgraph; +use crate::link::federation_spec_definition::FederationSpecDefinition; +use crate::link::federation_spec_definition::FEDERATION_VERSIONS; +use crate::link::join_spec_definition::FieldDirectiveArguments; +use crate::link::join_spec_definition::JoinSpecDefinition; +use crate::link::join_spec_definition::TypeDirectiveArguments; +use crate::link::spec::Identity; +use crate::link::spec::Version; +use crate::link::spec_definition::SpecDefinition; +use crate::schema::field_set::parse_field_set_without_normalization; +use crate::schema::position::is_graphql_reserved_name; +use crate::schema::position::CompositeTypeDefinitionPosition; +use crate::schema::position::DirectiveDefinitionPosition; +use crate::schema::position::EnumTypeDefinitionPosition; +use crate::schema::position::FieldDefinitionPosition; +use crate::schema::position::InputObjectFieldDefinitionPosition; +use crate::schema::position::InputObjectTypeDefinitionPosition; +use crate::schema::position::InterfaceTypeDefinitionPosition; +use crate::schema::position::ObjectFieldDefinitionPosition; +use crate::schema::position::ObjectOrInterfaceFieldDefinitionPosition; +use crate::schema::position::ObjectOrInterfaceTypeDefinitionPosition; +use crate::schema::position::ObjectTypeDefinitionPosition; +use crate::schema::position::SchemaRootDefinitionKind; +use crate::schema::position::SchemaRootDefinitionPosition; +use crate::schema::position::TypeDefinitionPosition; +use crate::schema::position::UnionTypeDefinitionPosition; +use crate::schema::type_and_directive_specification::FieldSpecification; +use crate::schema::type_and_directive_specification::ObjectTypeSpecification; +use crate::schema::type_and_directive_specification::ScalarTypeSpecification; +use crate::schema::type_and_directive_specification::TypeAndDirectiveSpecification; +use crate::schema::type_and_directive_specification::UnionTypeSpecification; +use crate::schema::FederationSchema; +use crate::schema::ValidFederationSchema; + +/// Assumes the given schema has been validated. +/// +/// TODO: A lot of common data gets passed around in the functions called by this one, considering +/// making an e.g. ExtractSubgraphs struct to contain the data. +pub(crate) fn extract_subgraphs_from_supergraph( + supergraph_schema: &FederationSchema, + validate_extracted_subgraphs: Option, +) -> Result { + let validate_extracted_subgraphs = validate_extracted_subgraphs.unwrap_or(true); + let (link_spec_definition, join_spec_definition) = + crate::validate_supergraph_for_query_planning(supergraph_schema)?; + let is_fed_1 = *join_spec_definition.version() == Version { major: 0, minor: 1 }; + let (mut subgraphs, federation_spec_definitions, graph_enum_value_name_to_subgraph_name) = + collect_empty_subgraphs(supergraph_schema, join_spec_definition)?; + + let mut filtered_types = Vec::new(); + for type_definition_position in supergraph_schema.get_types() { + if !join_spec_definition + .is_spec_type_name(supergraph_schema, type_definition_position.type_name())? + && !link_spec_definition + .is_spec_type_name(supergraph_schema, type_definition_position.type_name())? + { + filtered_types.push(type_definition_position); + } + } + if is_fed_1 { + // Handle Fed 1 supergraphs eventually, the extraction logic is gnarly + todo!() + } else { + extract_subgraphs_from_fed_2_supergraph( + supergraph_schema, + &mut subgraphs, + &graph_enum_value_name_to_subgraph_name, + &federation_spec_definitions, + join_spec_definition, + &filtered_types, + )?; + } + + for graph_enum_value in graph_enum_value_name_to_subgraph_name.keys() { + let subgraph = get_subgraph( + &mut subgraphs, + &graph_enum_value_name_to_subgraph_name, + graph_enum_value, + )?; + let federation_spec_definition = federation_spec_definitions + .get(graph_enum_value) + .ok_or_else(|| SingleFederationError::InvalidFederationSupergraph { + message: "Subgraph unexpectedly does not use federation spec".to_owned(), + })?; + add_federation_operations(subgraph, federation_spec_definition)?; + } + + let mut valid_subgraphs = ValidFederationSubgraphs::new(); + for (_, mut subgraph) in subgraphs { + let valid_subgraph_schema = if validate_extracted_subgraphs { + match subgraph.schema.validate_or_return_self() { + Ok(schema) => schema, + Err((schema, error)) => { + subgraph.schema = schema; + if is_fed_1 { + // See message above about Fed 1 supergraphs + todo!() + } else { + let mut message = format!( + "Unexpected error extracting {} from the supergraph: this is either a bug, or the supergraph has been corrupted.\n\nDetails:\n{error}", + subgraph.name, + ); + maybe_dump_subgraph_schema(subgraph, &mut message); + return Err( + SingleFederationError::InvalidFederationSupergraph { message }.into(), + ); + } + } + } + } else { + subgraph.schema.assume_valid()? + }; + valid_subgraphs.add(ValidFederationSubgraph { + name: subgraph.name, + url: subgraph.url, + schema: valid_subgraph_schema, + })?; + } + + Ok(valid_subgraphs) +} + +type CollectEmptySubgraphsOk = ( + FederationSubgraphs, + IndexMap, + IndexMap, +); +fn collect_empty_subgraphs( + supergraph_schema: &FederationSchema, + join_spec_definition: &JoinSpecDefinition, +) -> Result { + let mut subgraphs = FederationSubgraphs::new(); + let graph_directive_definition = + join_spec_definition.graph_directive_definition(supergraph_schema)?; + let graph_enum = join_spec_definition.graph_enum_definition(supergraph_schema)?; + let mut federation_spec_definitions = IndexMap::new(); + let mut graph_enum_value_name_to_subgraph_name = IndexMap::new(); + for (enum_value_name, enum_value_definition) in graph_enum.values.iter() { + let graph_application = enum_value_definition + .directives + .get(&graph_directive_definition.name) + .ok_or_else(|| SingleFederationError::InvalidFederationSupergraph { + message: format!( + "Value \"{}\" of join__Graph enum has no @join__graph directive", + enum_value_name + ), + })?; + let graph_arguments = join_spec_definition.graph_directive_arguments(graph_application)?; + let subgraph = FederationSubgraph { + name: graph_arguments.name.as_str().to_owned(), + url: graph_arguments.url.as_str().to_owned(), + schema: new_empty_fed_2_subgraph_schema()?, + }; + let federation_link = &subgraph + .schema + .metadata() + .as_ref() + .and_then(|metadata| metadata.for_identity(&Identity::federation_identity())) + .ok_or_else(|| SingleFederationError::InvalidFederationSupergraph { + message: "Subgraph unexpectedly does not use federation spec".to_owned(), + })?; + let federation_spec_definition = FEDERATION_VERSIONS + .find(&federation_link.url.version) + .ok_or_else(|| SingleFederationError::InvalidFederationSupergraph { + message: "Subgraph unexpectedly does not use a supported federation spec version" + .to_owned(), + })?; + subgraphs.add(subgraph)?; + graph_enum_value_name_to_subgraph_name + .insert(enum_value_name.clone(), graph_arguments.name); + federation_spec_definitions.insert(enum_value_name.clone(), federation_spec_definition); + } + Ok(( + subgraphs, + federation_spec_definitions, + graph_enum_value_name_to_subgraph_name, + )) +} + +/// TODO: Use the JS/programmatic approach instead of hard-coding definitions. +pub(crate) fn new_empty_fed_2_subgraph_schema() -> Result { + let builder = SchemaBuilder::new().adopt_orphan_extensions(); + let builder = builder.parse( + r#" + extend schema + @link(url: "https://specs.apollo.dev/link/v1.0") + @link(url: "https://specs.apollo.dev/federation/v2.5") + + directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA + + scalar link__Import + + enum link__Purpose { + """ + \`SECURITY\` features provide metadata necessary to securely resolve fields. + """ + SECURITY + + """ + \`EXECUTION\` features provide metadata necessary for operation execution. + """ + EXECUTION + } + + directive @federation__key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE + + directive @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION + + directive @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION + + directive @federation__external(reason: String) on OBJECT | FIELD_DEFINITION + + directive @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA + + directive @federation__extends on OBJECT | INTERFACE + + directive @federation__shareable on OBJECT | FIELD_DEFINITION + + directive @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION + + directive @federation__override(from: String!) on FIELD_DEFINITION + + directive @federation__composeDirective(name: String) repeatable on SCHEMA + + directive @federation__interfaceObject on OBJECT + + directive @federation__authenticated on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM + + directive @federation__requiresScopes(scopes: [[federation__Scope!]!]!) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM + + scalar federation__FieldSet + + scalar federation__Scope + "#, + "subgraph.graphql", + ); + FederationSchema::new(builder.build()?) +} + +struct TypeInfo { + name: NamedType, + // HashMap + subgraph_info: IndexMap, +} + +struct TypeInfos { + object_types: Vec, + interface_types: Vec, + union_types: Vec, + enum_types: Vec, + input_object_types: Vec, +} + +fn extract_subgraphs_from_fed_2_supergraph( + supergraph_schema: &FederationSchema, + subgraphs: &mut FederationSubgraphs, + graph_enum_value_name_to_subgraph_name: &IndexMap, + federation_spec_definitions: &IndexMap, + join_spec_definition: &'static JoinSpecDefinition, + filtered_types: &Vec, +) -> Result<(), FederationError> { + let TypeInfos { + object_types, + interface_types, + union_types, + enum_types, + input_object_types, + } = add_all_empty_subgraph_types( + supergraph_schema, + subgraphs, + graph_enum_value_name_to_subgraph_name, + federation_spec_definitions, + join_spec_definition, + filtered_types, + )?; + + extract_object_type_content( + supergraph_schema, + subgraphs, + graph_enum_value_name_to_subgraph_name, + federation_spec_definitions, + join_spec_definition, + &object_types, + )?; + extract_interface_type_content( + supergraph_schema, + subgraphs, + graph_enum_value_name_to_subgraph_name, + federation_spec_definitions, + join_spec_definition, + &interface_types, + )?; + extract_union_type_content( + supergraph_schema, + subgraphs, + graph_enum_value_name_to_subgraph_name, + join_spec_definition, + &union_types, + )?; + extract_enum_type_content( + supergraph_schema, + subgraphs, + graph_enum_value_name_to_subgraph_name, + join_spec_definition, + &enum_types, + )?; + extract_input_object_type_content( + supergraph_schema, + subgraphs, + graph_enum_value_name_to_subgraph_name, + join_spec_definition, + &input_object_types, + )?; + + // We add all the "executable" directive definitions from the supergraph to each subgraphs, as + // those may be part of a query and end up in any subgraph fetches. We do this "last" to make + // sure that if one of the directives uses a type for an argument, that argument exists. Note + // that we don't bother with non-executable directive definitions at the moment since we + // don't extract their applications. It might become something we need later, but we don't so + // far. Accordingly, we skip any potentially applied directives in the argument of the copied + // definition, because we haven't copied type-system directives. + let all_executable_directive_definitions = supergraph_schema + .schema() + .directive_definitions + .values() + .filter_map(|directive_definition| { + let executable_locations = directive_definition + .locations + .iter() + .filter(|location| EXECUTABLE_DIRECTIVE_LOCATIONS.contains(*location)) + .copied() + .collect::>(); + if executable_locations.is_empty() { + return None; + } + Some(Node::new(DirectiveDefinition { + description: None, + name: directive_definition.name.clone(), + arguments: directive_definition + .arguments + .iter() + .map(|argument| { + Node::new(InputValueDefinition { + description: None, + name: argument.name.clone(), + ty: argument.ty.clone(), + default_value: argument.default_value.clone(), + directives: Default::default(), + }) + }) + .collect::>(), + repeatable: directive_definition.repeatable, + locations: executable_locations, + })) + }) + .collect::>(); + for subgraph in subgraphs.subgraphs.values_mut() { + remove_inactive_requires_and_provides_from_subgraph(&mut subgraph.schema)?; + remove_unused_types_from_subgraph(&mut subgraph.schema)?; + for definition in all_executable_directive_definitions.iter() { + DirectiveDefinitionPosition { + directive_name: definition.name.clone(), + } + .insert(&mut subgraph.schema, definition.clone())?; + } + } + + Ok(()) +} + +fn add_all_empty_subgraph_types( + supergraph_schema: &FederationSchema, + subgraphs: &mut FederationSubgraphs, + graph_enum_value_name_to_subgraph_name: &IndexMap, + federation_spec_definitions: &IndexMap, + join_spec_definition: &'static JoinSpecDefinition, + filtered_types: &Vec, +) -> Result { + let type_directive_definition = + join_spec_definition.type_directive_definition(supergraph_schema)?; + + let mut object_types: Vec = Vec::new(); + let mut interface_types: Vec = Vec::new(); + let mut union_types: Vec = Vec::new(); + let mut enum_types: Vec = Vec::new(); + let mut input_object_types: Vec = Vec::new(); + + for type_definition_position in filtered_types { + let type_ = type_definition_position.get(supergraph_schema.schema())?; + let mut type_directive_applications = Vec::new(); + for directive in type_.directives().get_all(&type_directive_definition.name) { + type_directive_applications + .push(join_spec_definition.type_directive_arguments(directive)?); + } + let types_mut = match &type_definition_position { + TypeDefinitionPosition::Scalar(pos) => { + // Scalar are a bit special in that they don't have any sub-component, so we don't + // track them beyond adding them to the proper subgraphs. It's also simple because + // there is no possible key so there is exactly one @join__type application for each + // subgraph having the scalar (and most arguments cannot be present). + for type_directive_application in &type_directive_applications { + let subgraph = get_subgraph( + subgraphs, + graph_enum_value_name_to_subgraph_name, + &type_directive_application.graph, + )?; + pos.pre_insert(&mut subgraph.schema)?; + pos.insert( + &mut subgraph.schema, + Node::new(ScalarType { + description: None, + name: pos.type_name.clone(), + directives: Default::default(), + }), + )?; + } + None + } + TypeDefinitionPosition::Object(_) => Some(&mut object_types), + TypeDefinitionPosition::Interface(_) => Some(&mut interface_types), + TypeDefinitionPosition::Union(_) => Some(&mut union_types), + TypeDefinitionPosition::Enum(_) => Some(&mut enum_types), + TypeDefinitionPosition::InputObject(_) => Some(&mut input_object_types), + }; + if let Some(types_mut) = types_mut { + types_mut.push(add_empty_type( + type_definition_position.clone(), + &type_directive_applications, + subgraphs, + graph_enum_value_name_to_subgraph_name, + federation_spec_definitions, + )?); + } + } + + Ok(TypeInfos { + object_types, + interface_types, + union_types, + enum_types, + input_object_types, + }) +} + +fn add_empty_type( + type_definition_position: TypeDefinitionPosition, + type_directive_applications: &Vec, + subgraphs: &mut FederationSubgraphs, + graph_enum_value_name_to_subgraph_name: &IndexMap, + federation_spec_definitions: &IndexMap, +) -> Result { + // In fed2, we always mark all types with `@join__type` but making sure. + if type_directive_applications.is_empty() { + return Err(SingleFederationError::InvalidFederationSupergraph { + message: format!("Missing @join__type on \"{}\"", type_definition_position), + } + .into()); + } + let mut type_info = TypeInfo { + name: type_definition_position.type_name().clone(), + subgraph_info: IndexMap::new(), + }; + for type_directive_application in type_directive_applications { + let subgraph = get_subgraph( + subgraphs, + graph_enum_value_name_to_subgraph_name, + &type_directive_application.graph, + )?; + let federation_spec_definition = federation_spec_definitions + .get(&type_directive_application.graph) + .ok_or_else(|| SingleFederationError::Internal { + message: format!( + "Missing federation spec info for subgraph enum value \"{}\"", + type_directive_application.graph + ), + })?; + + if !type_info + .subgraph_info + .contains_key(&type_directive_application.graph) + { + let mut is_interface_object = false; + match &type_definition_position { + TypeDefinitionPosition::Scalar(_) => { + return Err(SingleFederationError::Internal { + message: "\"add_empty_type()\" shouldn't be called for scalars".to_owned(), + } + .into()); + } + TypeDefinitionPosition::Object(pos) => { + pos.pre_insert(&mut subgraph.schema)?; + pos.insert( + &mut subgraph.schema, + Node::new(ObjectType { + description: None, + name: pos.type_name.clone(), + implements_interfaces: Default::default(), + directives: Default::default(), + fields: Default::default(), + }), + )?; + if pos.type_name == "Query" { + let root_pos = SchemaRootDefinitionPosition { + root_kind: SchemaRootDefinitionKind::Query, + }; + if root_pos.try_get(subgraph.schema.schema()).is_none() { + root_pos.insert( + &mut subgraph.schema, + ComponentName::from(&pos.type_name), + )?; + } + } else if pos.type_name == "Mutation" { + let root_pos = SchemaRootDefinitionPosition { + root_kind: SchemaRootDefinitionKind::Mutation, + }; + if root_pos.try_get(subgraph.schema.schema()).is_none() { + root_pos.insert( + &mut subgraph.schema, + ComponentName::from(&pos.type_name), + )?; + } + } else if pos.type_name == "Subscription" { + let root_pos = SchemaRootDefinitionPosition { + root_kind: SchemaRootDefinitionKind::Subscription, + }; + if root_pos.try_get(subgraph.schema.schema()).is_none() { + root_pos.insert( + &mut subgraph.schema, + ComponentName::from(&pos.type_name), + )?; + } + } + } + TypeDefinitionPosition::Interface(pos) => { + if type_directive_application.is_interface_object { + is_interface_object = true; + let interface_object_directive = federation_spec_definition + .interface_object_directive(&subgraph.schema)?; + let pos = ObjectTypeDefinitionPosition { + type_name: pos.type_name.clone(), + }; + pos.pre_insert(&mut subgraph.schema)?; + pos.insert( + &mut subgraph.schema, + Node::new(ObjectType { + description: None, + name: pos.type_name.clone(), + implements_interfaces: Default::default(), + directives: DirectiveList(vec![Component::new( + interface_object_directive, + )]), + fields: Default::default(), + }), + )?; + } else { + pos.pre_insert(&mut subgraph.schema)?; + pos.insert( + &mut subgraph.schema, + Node::new(InterfaceType { + description: None, + name: pos.type_name.clone(), + implements_interfaces: Default::default(), + directives: Default::default(), + fields: Default::default(), + }), + )?; + } + } + TypeDefinitionPosition::Union(pos) => { + pos.pre_insert(&mut subgraph.schema)?; + pos.insert( + &mut subgraph.schema, + Node::new(UnionType { + description: None, + name: pos.type_name.clone(), + directives: Default::default(), + members: Default::default(), + }), + )?; + } + TypeDefinitionPosition::Enum(pos) => { + pos.pre_insert(&mut subgraph.schema)?; + pos.insert( + &mut subgraph.schema, + Node::new(EnumType { + description: None, + name: pos.type_name.clone(), + directives: Default::default(), + values: Default::default(), + }), + )?; + } + TypeDefinitionPosition::InputObject(pos) => { + pos.pre_insert(&mut subgraph.schema)?; + pos.insert( + &mut subgraph.schema, + Node::new(InputObjectType { + description: None, + name: pos.type_name.clone(), + directives: Default::default(), + fields: Default::default(), + }), + )?; + } + }; + type_info.subgraph_info.insert( + type_directive_application.graph.clone(), + is_interface_object, + ); + } + + if let Some(key) = &type_directive_application.key { + let mut key_directive = Component::new(federation_spec_definition.key_directive( + &subgraph.schema, + key.clone(), + type_directive_application.resolvable, + )?); + if type_directive_application.extension { + key_directive.origin = + ComponentOrigin::Extension(ExtensionId::new(&key_directive.node)) + } + let subgraph_type_definition_position = subgraph + .schema + .get_type(type_definition_position.type_name().clone())?; + match &subgraph_type_definition_position { + TypeDefinitionPosition::Scalar(_) => { + return Err(SingleFederationError::Internal { + message: "\"add_empty_type()\" shouldn't be called for scalars".to_owned(), + } + .into()); + } + TypeDefinitionPosition::Object(pos) => { + pos.insert_directive(&mut subgraph.schema, key_directive)?; + } + TypeDefinitionPosition::Interface(pos) => { + pos.insert_directive(&mut subgraph.schema, key_directive)?; + } + TypeDefinitionPosition::Union(pos) => { + pos.insert_directive(&mut subgraph.schema, key_directive)?; + } + TypeDefinitionPosition::Enum(pos) => { + pos.insert_directive(&mut subgraph.schema, key_directive)?; + } + TypeDefinitionPosition::InputObject(pos) => { + pos.insert_directive(&mut subgraph.schema, key_directive)?; + } + }; + } + } + + Ok(type_info) +} + +fn extract_object_type_content( + supergraph_schema: &FederationSchema, + subgraphs: &mut FederationSubgraphs, + graph_enum_value_name_to_subgraph_name: &IndexMap, + federation_spec_definitions: &IndexMap, + join_spec_definition: &JoinSpecDefinition, + info: &[TypeInfo], +) -> Result<(), FederationError> { + let field_directive_definition = + join_spec_definition.field_directive_definition(supergraph_schema)?; + // join__implements was added in join 0.2, and this method does not run for join 0.1, so it + // should be defined. + let implements_directive_definition = join_spec_definition + .implements_directive_definition(supergraph_schema)? + .ok_or_else(|| SingleFederationError::InvalidFederationSupergraph { + message: "@join__implements should exist for a fed2 supergraph".to_owned(), + })?; + + for TypeInfo { + name: type_name, + subgraph_info, + } in info.iter() + { + let pos = ObjectTypeDefinitionPosition { + type_name: (*type_name).clone(), + }; + let type_ = pos.get(supergraph_schema.schema())?; + + for directive in type_ + .directives + .get_all(&implements_directive_definition.name) + { + let implements_directive_application = + join_spec_definition.implements_directive_arguments(directive)?; + if !subgraph_info.contains_key(&implements_directive_application.graph) { + return Err( + SingleFederationError::InvalidFederationSupergraph { + message: format!( + "@join__implements cannot exist on \"{}\" for subgraph \"{}\" without type-level @join__type", + type_name, + implements_directive_application.graph, + ), + }.into() + ); + } + let subgraph = get_subgraph( + subgraphs, + graph_enum_value_name_to_subgraph_name, + &implements_directive_application.graph, + )?; + pos.insert_implements_interface( + &mut subgraph.schema, + ComponentName::from(Name::new(implements_directive_application.interface)?), + )?; + } + + for (field_name, field) in type_.fields.iter() { + let field_pos = pos.field(field_name.clone()); + let mut field_directive_applications = Vec::new(); + for directive in field.directives.get_all(&field_directive_definition.name) { + field_directive_applications + .push(join_spec_definition.field_directive_arguments(directive)?); + } + if field_directive_applications.is_empty() { + // In a fed2 subgraph, no @join__field means that the field is in all the subgraphs + // in which the type is. + let is_shareable = subgraph_info.len() > 1; + for graph_enum_value in subgraph_info.keys() { + let subgraph = get_subgraph( + subgraphs, + graph_enum_value_name_to_subgraph_name, + graph_enum_value, + )?; + let federation_spec_definition = federation_spec_definitions + .get(graph_enum_value) + .ok_or_else(|| SingleFederationError::InvalidFederationSupergraph { + message: "Subgraph unexpectedly does not use federation spec" + .to_owned(), + })?; + add_subgraph_field( + field_pos.clone().into(), + field, + subgraph, + federation_spec_definition, + is_shareable, + None, + )?; + } + } else { + let is_shareable = field_directive_applications + .iter() + .filter(|field_directive_application| { + !field_directive_application.external.unwrap_or(false) + && !field_directive_application.user_overridden.unwrap_or(false) + }) + .count() + > 1; + + for field_directive_application in &field_directive_applications { + let Some(graph_enum_value) = &field_directive_application.graph else { + // We use a @join__field with no graph to indicates when a field in the + // supergraph does not come directly from any subgraph and there is thus + // nothing to do to "extract" it. + continue; + }; + let subgraph = get_subgraph( + subgraphs, + graph_enum_value_name_to_subgraph_name, + graph_enum_value, + )?; + let federation_spec_definition = federation_spec_definitions + .get(graph_enum_value) + .ok_or_else(|| SingleFederationError::InvalidFederationSupergraph { + message: "Subgraph unexpectedly does not use federation spec" + .to_owned(), + })?; + if !subgraph_info.contains_key(graph_enum_value) { + return Err( + SingleFederationError::InvalidFederationSupergraph { + message: format!( + "@join__field cannot exist on {}.{} for subgraph {} without type-level @join__type", + type_name, + field_name, + graph_enum_value, + ), + }.into() + ); + } + add_subgraph_field( + field_pos.clone().into(), + field, + subgraph, + federation_spec_definition, + is_shareable, + Some(field_directive_application), + )?; + } + } + } + } + + Ok(()) +} + +fn extract_interface_type_content( + supergraph_schema: &FederationSchema, + subgraphs: &mut FederationSubgraphs, + graph_enum_value_name_to_subgraph_name: &IndexMap, + federation_spec_definitions: &IndexMap, + join_spec_definition: &JoinSpecDefinition, + info: &[TypeInfo], +) -> Result<(), FederationError> { + let field_directive_definition = + join_spec_definition.field_directive_definition(supergraph_schema)?; + // join_implements was added in join 0.2, and this method does not run for join 0.1, so it + // should be defined. + let implements_directive_definition = join_spec_definition + .implements_directive_definition(supergraph_schema)? + .ok_or_else(|| SingleFederationError::InvalidFederationSupergraph { + message: "@join__implements should exist for a fed2 supergraph".to_owned(), + })?; + + for TypeInfo { + name: type_name, + subgraph_info, + } in info.iter() + { + let type_ = InterfaceTypeDefinitionPosition { + type_name: (*type_name).clone(), + } + .get(supergraph_schema.schema())?; + fn get_pos( + subgraph: &FederationSubgraph, + subgraph_info: &IndexMap, + graph_enum_value: &Name, + type_name: NamedType, + ) -> Result { + let is_interface_object = *subgraph_info.get(graph_enum_value).ok_or_else(|| { + SingleFederationError::InvalidFederationSupergraph { + message: format!( + "@join__implements cannot exist on {} for subgraph {} without type-level @join__type", + type_name, + graph_enum_value, + ), + } + })?; + Ok(match subgraph.schema.get_type(type_name.clone())? { + TypeDefinitionPosition::Object(pos) => { + if !is_interface_object { + return Err( + SingleFederationError::Internal { + message: "\"extract_interface_type_content()\" encountered an unexpected interface object type in subgraph".to_owned(), + }.into() + ); + } + pos.into() + } + TypeDefinitionPosition::Interface(pos) => { + if is_interface_object { + return Err( + SingleFederationError::Internal { + message: "\"extract_interface_type_content()\" encountered an interface type in subgraph that should have been an interface object".to_owned(), + }.into() + ); + } + pos.into() + } + _ => { + return Err( + SingleFederationError::Internal { + message: "\"extract_interface_type_content()\" encountered non-object/interface type in subgraph".to_owned(), + }.into() + ); + } + }) + } + + for directive in type_ + .directives + .get_all(&implements_directive_definition.name) + { + let implements_directive_application = + join_spec_definition.implements_directive_arguments(directive)?; + let subgraph = get_subgraph( + subgraphs, + graph_enum_value_name_to_subgraph_name, + &implements_directive_application.graph, + )?; + let pos = get_pos( + subgraph, + subgraph_info, + &implements_directive_application.graph, + type_name.clone(), + )?; + match pos { + ObjectOrInterfaceTypeDefinitionPosition::Object(pos) => { + pos.insert_implements_interface( + &mut subgraph.schema, + ComponentName::from(Name::new( + &implements_directive_application.interface, + )?), + )?; + } + ObjectOrInterfaceTypeDefinitionPosition::Interface(pos) => { + pos.insert_implements_interface( + &mut subgraph.schema, + ComponentName::from(Name::new( + &implements_directive_application.interface, + )?), + )?; + } + } + } + + for (field_name, field) in type_.fields.iter() { + let mut field_directive_applications = Vec::new(); + for directive in field.directives.get_all(&field_directive_definition.name) { + field_directive_applications + .push(join_spec_definition.field_directive_arguments(directive)?); + } + if field_directive_applications.is_empty() { + // In a fed2 subgraph, no @join__field means that the field is in all the subgraphs + // in which the type is. + for graph_enum_value in subgraph_info.keys() { + let subgraph = get_subgraph( + subgraphs, + graph_enum_value_name_to_subgraph_name, + graph_enum_value, + )?; + let pos = + get_pos(subgraph, subgraph_info, graph_enum_value, type_name.clone())?; + let federation_spec_definition = federation_spec_definitions + .get(graph_enum_value) + .ok_or_else(|| SingleFederationError::InvalidFederationSupergraph { + message: "Subgraph unexpectedly does not use federation spec" + .to_owned(), + })?; + add_subgraph_field( + pos.field(field_name.clone()), + field, + subgraph, + federation_spec_definition, + false, + None, + )?; + } + } else { + for field_directive_application in &field_directive_applications { + let Some(graph_enum_value) = &field_directive_application.graph else { + // We use a @join__field with no graph to indicates when a field in the + // supergraph does not come directly from any subgraph and there is thus + // nothing to do to "extract" it. + continue; + }; + let subgraph = get_subgraph( + subgraphs, + graph_enum_value_name_to_subgraph_name, + graph_enum_value, + )?; + let pos = + get_pos(subgraph, subgraph_info, graph_enum_value, type_name.clone())?; + let federation_spec_definition = federation_spec_definitions + .get(graph_enum_value) + .ok_or_else(|| SingleFederationError::InvalidFederationSupergraph { + message: "Subgraph unexpectedly does not use federation spec" + .to_owned(), + })?; + if !subgraph_info.contains_key(graph_enum_value) { + return Err( + SingleFederationError::InvalidFederationSupergraph { + message: format!( + "@join__field cannot exist on {}.{} for subgraph {} without type-level @join__type", + type_name, + field_name, + graph_enum_value, + ), + }.into() + ); + } + add_subgraph_field( + pos.field(field_name.clone()), + field, + subgraph, + federation_spec_definition, + false, + Some(field_directive_application), + )?; + } + } + } + } + + Ok(()) +} + +fn extract_union_type_content( + supergraph_schema: &FederationSchema, + subgraphs: &mut FederationSubgraphs, + graph_enum_value_name_to_subgraph_name: &IndexMap, + join_spec_definition: &JoinSpecDefinition, + info: &[TypeInfo], +) -> Result<(), FederationError> { + // This was added in join 0.3, so it can genuinely be None. + let union_member_directive_definition = + join_spec_definition.union_member_directive_definition(supergraph_schema)?; + + // Note that union members works a bit differently from fields or enum values, and this because + // we cannot have directive applications on type members. So the `join_unionMember` directive + // applications are on the type itself, and they mention the member that they target. + for TypeInfo { + name: type_name, + subgraph_info, + } in info.iter() + { + let pos = UnionTypeDefinitionPosition { + type_name: (*type_name).clone(), + }; + let type_ = pos.get(supergraph_schema.schema())?; + + let mut union_member_directive_applications = Vec::new(); + if let Some(union_member_directive_definition) = union_member_directive_definition { + for directive in type_ + .directives + .get_all(&union_member_directive_definition.name) + { + union_member_directive_applications + .push(join_spec_definition.union_member_directive_arguments(directive)?); + } + } + if union_member_directive_applications.is_empty() { + // No @join__unionMember; every member should be added to every subgraph having the + // union (at least as long as the subgraph has the member itself). + for graph_enum_value in subgraph_info.keys() { + let subgraph = get_subgraph( + subgraphs, + graph_enum_value_name_to_subgraph_name, + graph_enum_value, + )?; + // Note that object types in the supergraph are guaranteed to be object types in + // subgraphs. + let subgraph_members = type_ + .members + .iter() + .filter(|member| { + subgraph + .schema + .schema() + .types + .contains_key((*member).deref()) + }) + .collect::>(); + for member in subgraph_members { + pos.insert_member(&mut subgraph.schema, ComponentName::from(&member.name))?; + } + } + } else { + for union_member_directive_application in &union_member_directive_applications { + let subgraph = get_subgraph( + subgraphs, + graph_enum_value_name_to_subgraph_name, + &union_member_directive_application.graph, + )?; + if !subgraph_info.contains_key(&union_member_directive_application.graph) { + return Err( + SingleFederationError::InvalidFederationSupergraph { + message: format!( + "@join__unionMember cannot exist on {} for subgraph {} without type-level @join__type", + type_name, + union_member_directive_application.graph, + ), + }.into() + ); + } + // Note that object types in the supergraph are guaranteed to be object types in + // subgraphs. We also know that the type must exist in this case (we don't generate + // broken @join__unionMember). + pos.insert_member( + &mut subgraph.schema, + ComponentName::from(Name::new(&union_member_directive_application.member)?), + )?; + } + } + } + + Ok(()) +} + +fn extract_enum_type_content( + supergraph_schema: &FederationSchema, + subgraphs: &mut FederationSubgraphs, + graph_enum_value_name_to_subgraph_name: &IndexMap, + join_spec_definition: &JoinSpecDefinition, + info: &[TypeInfo], +) -> Result<(), FederationError> { + // This was added in join 0.3, so it can genuinely be None. + let enum_value_directive_definition = + join_spec_definition.enum_value_directive_definition(supergraph_schema)?; + + for TypeInfo { + name: type_name, + subgraph_info, + } in info.iter() + { + let pos = EnumTypeDefinitionPosition { + type_name: (*type_name).clone(), + }; + let type_ = pos.get(supergraph_schema.schema())?; + + for (value_name, value) in type_.values.iter() { + let value_pos = pos.value(value_name.clone()); + let mut enum_value_directive_applications = Vec::new(); + if let Some(enum_value_directive_definition) = enum_value_directive_definition { + for directive in value + .directives + .get_all(&enum_value_directive_definition.name) + { + enum_value_directive_applications + .push(join_spec_definition.enum_value_directive_arguments(directive)?); + } + } + if enum_value_directive_applications.is_empty() { + for graph_enum_value in subgraph_info.keys() { + let subgraph = get_subgraph( + subgraphs, + graph_enum_value_name_to_subgraph_name, + graph_enum_value, + )?; + value_pos.insert( + &mut subgraph.schema, + Component::new(EnumValueDefinition { + description: None, + value: value_name.clone(), + directives: Default::default(), + }), + )?; + } + } else { + for enum_value_directive_application in &enum_value_directive_applications { + let subgraph = get_subgraph( + subgraphs, + graph_enum_value_name_to_subgraph_name, + &enum_value_directive_application.graph, + )?; + if !subgraph_info.contains_key(&enum_value_directive_application.graph) { + return Err( + SingleFederationError::InvalidFederationSupergraph { + message: format!( + "@join__enumValue cannot exist on {}.{} for subgraph {} without type-level @join__type", + type_name, + value_name, + enum_value_directive_application.graph, + ), + }.into() + ); + } + value_pos.insert( + &mut subgraph.schema, + Component::new(EnumValueDefinition { + description: None, + value: value_name.clone(), + directives: Default::default(), + }), + )?; + } + } + } + } + + Ok(()) +} + +fn extract_input_object_type_content( + supergraph_schema: &FederationSchema, + subgraphs: &mut FederationSubgraphs, + graph_enum_value_name_to_subgraph_name: &IndexMap, + join_spec_definition: &JoinSpecDefinition, + info: &[TypeInfo], +) -> Result<(), FederationError> { + let field_directive_definition = + join_spec_definition.field_directive_definition(supergraph_schema)?; + + for TypeInfo { + name: type_name, + subgraph_info, + } in info.iter() + { + let pos = InputObjectTypeDefinitionPosition { + type_name: (*type_name).clone(), + }; + let type_ = pos.get(supergraph_schema.schema())?; + + for (input_field_name, input_field) in type_.fields.iter() { + let input_field_pos = pos.field(input_field_name.clone()); + let mut field_directive_applications = Vec::new(); + for directive in input_field + .directives + .get_all(&field_directive_definition.name) + { + field_directive_applications + .push(join_spec_definition.field_directive_arguments(directive)?); + } + if field_directive_applications.is_empty() { + for graph_enum_value in subgraph_info.keys() { + let subgraph = get_subgraph( + subgraphs, + graph_enum_value_name_to_subgraph_name, + graph_enum_value, + )?; + add_subgraph_input_field(input_field_pos.clone(), input_field, subgraph, None)?; + } + } else { + for field_directive_application in &field_directive_applications { + let Some(graph_enum_value) = &field_directive_application.graph else { + // We use a @join__field with no graph to indicates when a field in the + // supergraph does not come directly from any subgraph and there is thus + // nothing to do to "extract" it. + continue; + }; + let subgraph = get_subgraph( + subgraphs, + graph_enum_value_name_to_subgraph_name, + graph_enum_value, + )?; + if !subgraph_info.contains_key(graph_enum_value) { + return Err( + SingleFederationError::InvalidFederationSupergraph { + message: format!( + "@join__field cannot exist on {}.{} for subgraph {} without type-level @join__type", + type_name, + input_field_name, + graph_enum_value, + ), + }.into() + ); + } + add_subgraph_input_field( + input_field_pos.clone(), + input_field, + subgraph, + Some(field_directive_application), + )?; + } + } + } + } + + Ok(()) +} + +fn add_subgraph_field( + object_or_interface_field_definition_position: ObjectOrInterfaceFieldDefinitionPosition, + field: &FieldDefinition, + subgraph: &mut FederationSubgraph, + federation_spec_definition: &'static FederationSpecDefinition, + is_shareable: bool, + field_directive_application: Option<&FieldDirectiveArguments>, +) -> Result<(), FederationError> { + let field_directive_application = + field_directive_application.unwrap_or_else(|| &FieldDirectiveArguments { + graph: None, + requires: None, + provides: None, + type_: None, + external: None, + override_: None, + user_overridden: None, + }); + let subgraph_field_type = match &field_directive_application.type_ { + Some(t) => decode_type(t)?, + None => field.ty.clone(), + }; + let mut subgraph_field = FieldDefinition { + description: None, + name: object_or_interface_field_definition_position + .field_name() + .clone(), + arguments: vec![], + ty: subgraph_field_type, + directives: Default::default(), + }; + + for argument in &field.arguments { + subgraph_field + .arguments + .push(Node::new(InputValueDefinition { + description: None, + name: argument.name.clone(), + ty: argument.ty.clone(), + default_value: argument.default_value.clone(), + directives: Default::default(), + })) + } + if let Some(requires) = &field_directive_application.requires { + subgraph_field.directives.push(Node::new( + federation_spec_definition.requires_directive(&subgraph.schema, requires.clone())?, + )); + } + if let Some(provides) = &field_directive_application.provides { + subgraph_field.directives.push(Node::new( + federation_spec_definition.provides_directive(&subgraph.schema, provides.clone())?, + )); + } + let external = field_directive_application.external.unwrap_or(false); + if external { + subgraph_field.directives.push(Node::new( + federation_spec_definition.external_directive(&subgraph.schema, None)?, + )); + } + let user_overridden = field_directive_application.user_overridden.unwrap_or(false); + if user_overridden { + subgraph_field.directives.push(Node::new( + federation_spec_definition + .external_directive(&subgraph.schema, Some(NodeStr::new("[overridden]")))?, + )); + } + if let Some(override_) = &field_directive_application.override_ { + subgraph_field.directives.push(Node::new( + federation_spec_definition.override_directive(&subgraph.schema, override_.clone())?, + )); + } + if is_shareable && !external && !user_overridden { + subgraph_field.directives.push(Node::new( + federation_spec_definition.shareable_directive(&subgraph.schema)?, + )); + } + + match object_or_interface_field_definition_position { + ObjectOrInterfaceFieldDefinitionPosition::Object(pos) => { + pos.insert(&mut subgraph.schema, Component::from(subgraph_field))?; + } + ObjectOrInterfaceFieldDefinitionPosition::Interface(pos) => { + pos.insert(&mut subgraph.schema, Component::from(subgraph_field))?; + } + }; + + Ok(()) +} + +fn add_subgraph_input_field( + input_object_field_definition_position: InputObjectFieldDefinitionPosition, + input_field: &InputValueDefinition, + subgraph: &mut FederationSubgraph, + field_directive_application: Option<&FieldDirectiveArguments>, +) -> Result<(), FederationError> { + let field_directive_application = + field_directive_application.unwrap_or_else(|| &FieldDirectiveArguments { + graph: None, + requires: None, + provides: None, + type_: None, + external: None, + override_: None, + user_overridden: None, + }); + let subgraph_input_field_type = match &field_directive_application.type_ { + Some(t) => Node::new(decode_type(t)?), + None => input_field.ty.clone(), + }; + let subgraph_input_field = InputValueDefinition { + description: None, + name: input_object_field_definition_position.field_name.clone(), + ty: subgraph_input_field_type, + default_value: input_field.default_value.clone(), + directives: Default::default(), + }; + + input_object_field_definition_position + .insert(&mut subgraph.schema, Component::from(subgraph_input_field))?; + + Ok(()) +} + +/// Parse a string encoding a type reference. +fn decode_type(type_: &str) -> Result { + Type::parse(type_, "").map_err(|_| { + SingleFederationError::InvalidGraphQL { + message: format!("Cannot parse type \"{}\"", type_), + } + .into() + }) +} + +fn get_subgraph<'subgraph>( + subgraphs: &'subgraph mut FederationSubgraphs, + graph_enum_value_name_to_subgraph_name: &IndexMap, + graph_enum_value: &Name, +) -> Result<&'subgraph mut FederationSubgraph, FederationError> { + let subgraph_name = graph_enum_value_name_to_subgraph_name + .get(graph_enum_value) + .ok_or_else(|| { + SingleFederationError::Internal { + message: format!( + "Invalid graph enum_value \"{}\": does not match an enum value defined in the @join__Graph enum", + graph_enum_value, + ), + } + })?; + subgraphs.get_mut(subgraph_name).ok_or_else(|| { + SingleFederationError::Internal { + message: "All subgraphs should have been created by \"collect_empty_subgraphs()\"" + .to_owned(), + } + .into() + }) +} + +struct FederationSubgraph { + name: String, + url: String, + schema: FederationSchema, +} + +struct FederationSubgraphs { + subgraphs: BTreeMap, +} + +impl FederationSubgraphs { + fn new() -> Self { + FederationSubgraphs { + subgraphs: BTreeMap::new(), + } + } + + fn add(&mut self, subgraph: FederationSubgraph) -> Result<(), FederationError> { + if self.subgraphs.contains_key(&subgraph.name) { + return Err(SingleFederationError::InvalidFederationSupergraph { + message: format!("A subgraph named \"{}\" already exists", subgraph.name), + } + .into()); + } + self.subgraphs.insert(subgraph.name.clone(), subgraph); + Ok(()) + } + + fn get(&self, name: &str) -> Option<&FederationSubgraph> { + self.subgraphs.get(name) + } + + fn get_mut(&mut self, name: &str) -> Option<&mut FederationSubgraph> { + self.subgraphs.get_mut(name) + } +} + +impl IntoIterator for FederationSubgraphs { + type Item = as IntoIterator>::Item; + type IntoIter = as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.subgraphs.into_iter() + } +} + +// TODO(@goto-bus-stop): consider an appropriate name for this in the public API +// TODO(@goto-bus-stop): should this exist separately from the `crate::subgraph::Subgraph` type? +#[derive(Debug)] +pub struct ValidFederationSubgraph { + pub name: String, + pub url: String, + pub schema: ValidFederationSchema, +} + +pub struct ValidFederationSubgraphs { + subgraphs: BTreeMap, +} + +impl fmt::Debug for ValidFederationSubgraphs { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("ValidFederationSubgraphs ")?; + f.debug_map().entries(self.subgraphs.iter()).finish() + } +} + +impl ValidFederationSubgraphs { + pub(crate) fn new() -> Self { + ValidFederationSubgraphs { + subgraphs: BTreeMap::new(), + } + } + + pub(crate) fn add(&mut self, subgraph: ValidFederationSubgraph) -> Result<(), FederationError> { + if self.subgraphs.contains_key(&subgraph.name) { + return Err(SingleFederationError::InvalidFederationSupergraph { + message: format!("A subgraph named \"{}\" already exists", subgraph.name), + } + .into()); + } + self.subgraphs.insert(subgraph.name.clone(), subgraph); + Ok(()) + } + + pub fn get(&self, name: &str) -> Option<&ValidFederationSubgraph> { + self.subgraphs.get(name) + } +} + +impl IntoIterator for ValidFederationSubgraphs { + type Item = as IntoIterator>::Item; + type IntoIter = as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.subgraphs.into_iter() + } +} + +lazy_static! { + static ref EXECUTABLE_DIRECTIVE_LOCATIONS: IndexSet = { + IndexSet::from([ + DirectiveLocation::Query, + DirectiveLocation::Mutation, + DirectiveLocation::Subscription, + DirectiveLocation::Field, + DirectiveLocation::FragmentDefinition, + DirectiveLocation::FragmentSpread, + DirectiveLocation::InlineFragment, + DirectiveLocation::VariableDefinition, + ]) + }; +} + +fn remove_unused_types_from_subgraph(schema: &mut FederationSchema) -> Result<(), FederationError> { + // We now do an additional path on all types because we sometimes added types to subgraphs + // without being sure that the subgraph had the type in the first place (especially with the + // join 0.1 spec), and because we later might not have added any fields/members to said type, + // they may be empty (indicating they clearly didn't belong to the subgraph in the first) and we + // need to remove them. Note that need to do this _after_ the `add_external_fields()` call above + // since it may have added (external) fields to some of the types. + let mut type_definition_positions: Vec = Vec::new(); + for (type_name, type_) in schema.schema().types.iter() { + match type_ { + ExtendedType::Object(type_) => { + if type_.fields.is_empty() { + type_definition_positions.push( + ObjectTypeDefinitionPosition { + type_name: type_name.clone(), + } + .into(), + ); + } + } + ExtendedType::Interface(type_) => { + if type_.fields.is_empty() { + type_definition_positions.push( + InterfaceTypeDefinitionPosition { + type_name: type_name.clone(), + } + .into(), + ); + } + } + ExtendedType::Union(type_) => { + if type_.members.is_empty() { + type_definition_positions.push( + UnionTypeDefinitionPosition { + type_name: type_name.clone(), + } + .into(), + ); + } + } + ExtendedType::InputObject(type_) => { + if type_.fields.is_empty() { + type_definition_positions.push( + InputObjectTypeDefinitionPosition { + type_name: type_name.clone(), + } + .into(), + ); + } + } + _ => {} + } + } + + // Note that we have to use remove_recursive() or this could leave the subgraph invalid. But if + // the type was not in this subgraph, nothing that depends on it should be either. + for position in type_definition_positions { + match position { + TypeDefinitionPosition::Object(position) => { + position.remove_recursive(schema)?; + } + TypeDefinitionPosition::Interface(position) => { + position.remove_recursive(schema)?; + } + TypeDefinitionPosition::Union(position) => { + position.remove_recursive(schema)?; + } + TypeDefinitionPosition::InputObject(position) => { + position.remove_recursive(schema)?; + } + _ => { + return Err(SingleFederationError::Internal { + message: "Encountered type kind that shouldn't have been removed".to_owned(), + } + .into()); + } + } + } + + Ok(()) +} + +const FEDERATION_ANY_TYPE_NAME: Name = name!("_Any"); +const FEDERATION_SERVICE_TYPE_NAME: Name = name!("_Service"); +const FEDERATION_SDL_FIELD_NAME: Name = name!("sdl"); +const FEDERATION_ENTITY_TYPE_NAME: Name = name!("_Entity"); +const FEDERATION_SERVICE_FIELD_NAME: Name = name!("_service"); +const FEDERATION_ENTITIES_FIELD_NAME: Name = name!("_entities"); +pub(crate) const FEDERATION_REPRESENTATIONS_ARGUMENTS_NAME: Name = name!("representations"); +pub(crate) const FEDERATION_REPRESENTATIONS_VAR_NAME: Name = name!("representations"); + +const GRAPHQL_STRING_TYPE_NAME: Name = name!("String"); +const GRAPHQL_QUERY_TYPE_NAME: Name = name!("Query"); + +const ANY_TYPE_SPEC: ScalarTypeSpecification = ScalarTypeSpecification { + name: FEDERATION_ANY_TYPE_NAME, +}; + +const SERVICE_TYPE_SPEC: ObjectTypeSpecification = ObjectTypeSpecification { + name: FEDERATION_SERVICE_TYPE_NAME, + fields: |_schema| { + [FieldSpecification { + name: FEDERATION_SDL_FIELD_NAME, + ty: Type::Named(GRAPHQL_STRING_TYPE_NAME), + arguments: Default::default(), + }] + .into() + }, +}; + +const QUERY_TYPE_SPEC: ObjectTypeSpecification = ObjectTypeSpecification { + name: GRAPHQL_QUERY_TYPE_NAME, + fields: |_schema| Default::default(), // empty Query (fields should be added later) +}; + +// PORT_NOTE: The JS implementation gets the key directive definition from the schema, +// but we have it as a parameter. +fn collect_entity_members( + schema: &FederationSchema, + key_directive_definition: &Node, +) -> IndexSet { + schema + .schema() + .types + .iter() + .filter_map(|(type_name, type_)| { + let ExtendedType::Object(type_) = type_ else { + return None; + }; + if !type_.directives.has(&key_directive_definition.name) { + return None; + } + Some(ComponentName::from(type_name)) + }) + .collect::>() +} + +fn add_federation_operations( + subgraph: &mut FederationSubgraph, + federation_spec_definition: &'static FederationSpecDefinition, +) -> Result<(), FederationError> { + // the `_Any` and `_Service` Type + ANY_TYPE_SPEC.check_or_add(&mut subgraph.schema)?; + SERVICE_TYPE_SPEC.check_or_add(&mut subgraph.schema)?; + + // the `_Entity` Type + let key_directive_definition = + federation_spec_definition.key_directive_definition(&subgraph.schema)?; + let entity_members = collect_entity_members(&subgraph.schema, key_directive_definition); + let has_entity_type = !entity_members.is_empty(); + if has_entity_type { + UnionTypeSpecification { + name: FEDERATION_ENTITY_TYPE_NAME, + members: |_| entity_members.clone(), + } + .check_or_add(&mut subgraph.schema)?; + } + + // the `Query` Type + let query_root_pos = SchemaRootDefinitionPosition { + root_kind: SchemaRootDefinitionKind::Query, + }; + if query_root_pos.try_get(subgraph.schema.schema()).is_none() { + QUERY_TYPE_SPEC.check_or_add(&mut subgraph.schema)?; + query_root_pos.insert( + &mut subgraph.schema, + ComponentName::from(QUERY_TYPE_SPEC.name), + )?; + } + + // `Query._entities` (optional) + let query_root_type_name = query_root_pos.get(subgraph.schema.schema())?.name.clone(); + let entity_field_pos = ObjectFieldDefinitionPosition { + type_name: query_root_type_name.clone(), + field_name: FEDERATION_ENTITIES_FIELD_NAME, + }; + if has_entity_type { + entity_field_pos.insert( + &mut subgraph.schema, + Component::new(FieldDefinition { + description: None, + name: FEDERATION_ENTITIES_FIELD_NAME, + arguments: vec![Node::new(InputValueDefinition { + description: None, + name: FEDERATION_REPRESENTATIONS_ARGUMENTS_NAME, + ty: Node::new(Type::NonNullList(Box::new(Type::NonNullNamed( + FEDERATION_ANY_TYPE_NAME, + )))), + default_value: None, + directives: Default::default(), + })], + ty: Type::NonNullList(Box::new(Type::Named(FEDERATION_ENTITY_TYPE_NAME))), + directives: Default::default(), + }), + )?; + } else { + entity_field_pos.remove(&mut subgraph.schema)?; + } + + // `Query._service` + ObjectFieldDefinitionPosition { + type_name: query_root_type_name.clone(), + field_name: FEDERATION_SERVICE_FIELD_NAME, + } + .insert( + &mut subgraph.schema, + Component::new(FieldDefinition { + description: None, + name: FEDERATION_SERVICE_FIELD_NAME, + arguments: Vec::new(), + ty: Type::NonNullNamed(FEDERATION_SERVICE_TYPE_NAME), + directives: Default::default(), + }), + )?; + + Ok(()) +} + +/// It makes no sense to have a @requires/@provides on a non-external leaf field, and we usually +/// reject it during schema validation. But this function remove such fields for when: +/// 1. We extract subgraphs from a Fed 1 supergraph, where such validations haven't been run. +/// 2. Fed 1 subgraphs are upgraded to Fed 2 subgraphs. +/// +/// The reason we do this (and generally reject it) is that such @requires/@provides have a negative +/// impact on later query planning, because it sometimes make us try type-exploding some interfaces +/// unnecessarily. Besides, if a usage adds something useless, there is a chance it hasn't fully +/// understood something, and warning about that fact through an error is more helpful. +fn remove_inactive_requires_and_provides_from_subgraph( + schema: &mut FederationSchema, +) -> Result<(), FederationError> { + let federation_spec_definition = get_federation_spec_definition_from_subgraph(schema)?; + let requires_directive_definition_name = federation_spec_definition + .requires_directive_definition(schema)? + .name + .clone(); + let provides_directive_definition_name = federation_spec_definition + .provides_directive_definition(schema)? + .name + .clone(); + + let mut object_or_interface_field_definition_positions: Vec< + ObjectOrInterfaceFieldDefinitionPosition, + > = vec![]; + for type_pos in schema.get_types() { + // Ignore introspection types. + if is_graphql_reserved_name(type_pos.type_name()) { + continue; + } + + // Ignore non-object/interface types. + let Ok(type_pos): Result = type_pos.try_into() + else { + continue; + }; + + match type_pos { + ObjectOrInterfaceTypeDefinitionPosition::Object(type_pos) => { + object_or_interface_field_definition_positions.extend( + type_pos + .get(schema.schema())? + .fields + .keys() + .map(|field_name| type_pos.field(field_name.clone()).into()), + ) + } + ObjectOrInterfaceTypeDefinitionPosition::Interface(type_pos) => { + object_or_interface_field_definition_positions.extend( + type_pos + .get(schema.schema())? + .fields + .keys() + .map(|field_name| type_pos.field(field_name.clone()).into()), + ) + } + }; + } + + for pos in object_or_interface_field_definition_positions { + remove_inactive_applications( + schema, + federation_spec_definition, + FieldSetDirectiveKind::Requires, + &requires_directive_definition_name, + pos.clone(), + )?; + remove_inactive_applications( + schema, + federation_spec_definition, + FieldSetDirectiveKind::Provides, + &provides_directive_definition_name, + pos, + )?; + } + + Ok(()) +} + +enum FieldSetDirectiveKind { + Provides, + Requires, +} + +fn remove_inactive_applications( + schema: &mut FederationSchema, + federation_spec_definition: &'static FederationSpecDefinition, + directive_kind: FieldSetDirectiveKind, + name_in_schema: &Name, + object_or_interface_field_definition_position: ObjectOrInterfaceFieldDefinitionPosition, +) -> Result<(), FederationError> { + let mut replacement_directives = Vec::new(); + let field = object_or_interface_field_definition_position.get(schema.schema())?; + for directive in field.directives.get_all(name_in_schema) { + let (fields, parent_type_pos) = match directive_kind { + FieldSetDirectiveKind::Provides => { + let fields = federation_spec_definition + .provides_directive_arguments(directive)? + .fields; + let parent_type_pos: CompositeTypeDefinitionPosition = schema + .get_type(field.ty.inner_named_type().clone())? + .try_into()?; + (fields, parent_type_pos) + } + FieldSetDirectiveKind::Requires => { + let fields = federation_spec_definition + .requires_directive_arguments(directive)? + .fields; + let parent_type_pos: CompositeTypeDefinitionPosition = + object_or_interface_field_definition_position + .parent() + .clone() + .into(); + (fields, parent_type_pos) + } + }; + // TODO: The assume_valid_ref() here is non-ideal, in the sense that the error messages we + // get back during field set parsing may not be user-friendly. We can't really validate the + // schema here since the schema may not be fully valid when this function is called within + // extract_subgraphs_from_supergraph() (it would also incur significant performance loss). + // At best, we could try to shift this computation to after the subgraph schema validation + // step, but its unclear at this time whether performing this shift affects correctness (and + // it takes time to determine that). So for now, we keep this here. + let valid_schema = Valid::assume_valid_ref(schema.schema()); + // TODO: In the JS codebase, this function ends up getting additionally used in the schema + // upgrader, where parsing the field set may error. In such cases, we end up skipping those + // directives instead of returning error here, as it pollutes the list of error messages + // during composition (another site in composition will properly check for field set + // validity and give better error messaging). + let mut fields = parse_field_set_without_normalization( + valid_schema, + parent_type_pos.type_name().clone(), + &fields, + )?; + let is_modified = remove_non_external_leaf_fields(schema, &mut fields)?; + if is_modified { + let replacement_directive = if fields.selections.is_empty() { + None + } else { + let fields = NodeStr::from(fields.serialize().no_indent().to_string()); + Some(Node::new(match directive_kind { + FieldSetDirectiveKind::Provides => { + federation_spec_definition.provides_directive(schema, fields)? + } + FieldSetDirectiveKind::Requires => { + federation_spec_definition.requires_directive(schema, fields)? + } + })) + }; + replacement_directives.push((directive.clone(), replacement_directive)) + } + } + + for (old_directive, new_directive) in replacement_directives { + object_or_interface_field_definition_position.remove_directive(schema, &old_directive); + if let Some(new_directive) = new_directive { + object_or_interface_field_definition_position + .insert_directive(schema, new_directive)?; + } + } + Ok(()) +} + +/// Removes any non-external leaf fields from the selection set, returning true if the selection +/// set was modified. +fn remove_non_external_leaf_fields( + schema: &FederationSchema, + selection_set: &mut executable::SelectionSet, +) -> Result { + let federation_spec_definition = get_federation_spec_definition_from_subgraph(schema)?; + let external_directive_definition_name = federation_spec_definition + .external_directive_definition(schema)? + .name + .clone(); + remove_non_external_leaf_fields_internal( + schema, + &external_directive_definition_name, + selection_set, + ) +} + +fn remove_non_external_leaf_fields_internal( + schema: &FederationSchema, + external_directive_definition_name: &Name, + selection_set: &mut executable::SelectionSet, +) -> Result { + let mut is_modified = false; + let mut errors = MultipleFederationErrors { errors: Vec::new() }; + selection_set.selections.retain_mut(|selection| { + let child_selection_set = match selection { + executable::Selection::Field(field) => { + match is_external_or_has_external_implementations( + schema, + external_directive_definition_name, + &selection_set.ty, + field, + ) { + Ok(is_external) => { + if is_external { + // Either the field or one of its implementors is external, so we keep + // the entire selection in that case. + return true; + } + } + Err(error) => { + errors.push(error); + return false; + } + }; + if field.selection_set.selections.is_empty() { + // An empty selection set means this is a leaf field. We would have returned + // earlier if this were external, so this is a non-external leaf field. + is_modified = true; + return false; + } + &mut field.make_mut().selection_set + } + executable::Selection::InlineFragment(inline_fragment) => { + &mut inline_fragment.make_mut().selection_set + } + executable::Selection::FragmentSpread(_) => { + errors.push( + SingleFederationError::Internal { + message: "Unexpectedly found named fragment in FieldSet scalar".to_owned(), + } + .into(), + ); + return false; + } + }; + // At this point, we either have a non-leaf non-external field, or an inline fragment. In + // either case, we recurse into its selection set. + match remove_non_external_leaf_fields_internal( + schema, + external_directive_definition_name, + child_selection_set, + ) { + Ok(is_child_modified) => { + if is_child_modified { + is_modified = true; + } + } + Err(error) => { + errors.push(error); + return false; + } + } + // If the recursion resulted in the selection set becoming empty, we remove this selection. + // Note that it shouldn't have started out empty, so if it became empty, is_child_modified + // would have been true, which means is_modified has already been set appropriately. + !child_selection_set.selections.is_empty() + }); + if errors.errors.is_empty() { + Ok(is_modified) + } else { + Err(errors.into()) + } +} + +fn is_external_or_has_external_implementations( + schema: &FederationSchema, + external_directive_definition_name: &Name, + parent_type_name: &NamedType, + selection: &Node, +) -> Result { + let type_pos: CompositeTypeDefinitionPosition = + schema.get_type(parent_type_name.clone())?.try_into()?; + let field_pos = type_pos.field(selection.name.clone())?; + let field = field_pos.get(schema.schema())?; + if field.directives.has(external_directive_definition_name) { + return Ok(true); + } + if let FieldDefinitionPosition::Interface(field_pos) = field_pos { + for runtime_object_pos in schema.possible_runtime_types(field_pos.parent().into())? { + let runtime_field_pos = runtime_object_pos.field(field_pos.field_name.clone()); + let runtime_field = runtime_field_pos.get(schema.schema())?; + if runtime_field + .directives + .has(external_directive_definition_name) + { + return Ok(true); + } + } + } + Ok(false) +} + +static DEBUG_SUBGRAPHS_ENV_VARIABLE_NAME: &str = "APOLLO_FEDERATION_DEBUG_SUBGRAPHS"; + +fn maybe_dump_subgraph_schema(subgraph: FederationSubgraph, message: &mut String) { + // NOTE: The std::fmt::write returns an error, but writing to a string will never return an + // error, so the result is dropped. + _ = match std::env::var(DEBUG_SUBGRAPHS_ENV_VARIABLE_NAME).map(|v| v.parse::()) { + Ok(Ok(true)) => { + let time = OffsetDateTime::now_local().unwrap_or_else(|_| OffsetDateTime::now_utc()); + let filename = format!("extracted-subgraph-{}-{time}.graphql", subgraph.name,); + let contents = subgraph.schema.schema().to_string(); + match std::fs::write(&filename, contents) { + Ok(_) => write!( + message, + "The (invalid) extracted subgraph has been written in: {filename}." + ), + Err(e) => write!( + message, + r#"Was not able to print generated subgraph for "{}" because: {e}"#, + subgraph.name + ), + } + } + _ => write!( + message, + "Re-run with environment variable '{}' set to 'true' to extract the invalid subgraph", + DEBUG_SUBGRAPHS_ENV_VARIABLE_NAME + ), + }; +} + +#[cfg(test)] +mod tests { + use apollo_compiler::name; + use apollo_compiler::Schema; + + use crate::schema::FederationSchema; + use crate::ValidFederationSubgraphs; + + // JS PORT NOTE: these tests were ported from + // https://github.com/apollographql/federation/blob/3e2c845c74407a136b9e0066e44c1ad1467d3013/internals-js/src/__tests__/extractSubgraphsFromSupergraph.test.ts + + #[test] + fn handles_types_having_no_fields_referenced_by_other_interfaces_in_a_subgraph_correctly() { + /* + * JS PORT NOTE: the original test used a Federation 1 supergraph. + * The following supergraph has been generated from: + + federation_version: =2.6.0 + subgraphs: + a: + routing_url: http://a + schema: + sdl: | + type Query { + q: A + } + + interface A { + a: B + } + + type B { + b: C @provides(fields: "c") + } + + type C { + c: String + } + b: + routing_url: http://b + schema: + sdl: | + type C { + c: String + } + c: + routing_url: http://c + schema: + sdl: | + type D { + d: String + } + + * This tests is almost identical to the 'handles types having no fields referenced by other objects in a subgraph correctly' + * one, except that the reference to the type being removed is in an interface, to make double-sure this case is + * handled as well. + */ + + let supergraph = r#" + schema + @link(url: "https://specs.apollo.dev/link/v1.0") + @link(url: "https://specs.apollo.dev/join/v0.3", for: EXECUTION) + { + query: Query + } + + directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE + + directive @join__field(graph: join__Graph, requires: join__FieldSet, provides: join__FieldSet, type: String, external: Boolean, override: String, usedOverridden: Boolean) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION + + directive @join__graph(name: String!, url: String!) on ENUM_VALUE + + directive @join__implements(graph: join__Graph!, interface: String!) repeatable on OBJECT | INTERFACE + + directive @join__type(graph: join__Graph!, key: join__FieldSet, extension: Boolean! = false, resolvable: Boolean! = true, isInterfaceObject: Boolean! = false) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR + + directive @join__unionMember(graph: join__Graph!, member: String!) repeatable on UNION + + directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA + + interface A + @join__type(graph: A) + { + a: B + } + + type B + @join__type(graph: A) + { + b: C + } + + type C + @join__type(graph: A) + @join__type(graph: B) + { + c: String + } + + type D + @join__type(graph: C) + { + d: String + } + + scalar join__FieldSet + + enum join__Graph { + A @join__graph(name: "a", url: "http://a") + B @join__graph(name: "b", url: "http://b") + C @join__graph(name: "c", url: "http://c") + } + + scalar link__Import + + enum link__Purpose { + """ + `SECURITY` features provide metadata necessary to securely resolve fields. + """ + SECURITY + + """ + `EXECUTION` features provide metadata necessary for operation execution. + """ + EXECUTION + } + + type Query + @join__type(graph: A) + @join__type(graph: B) + @join__type(graph: C) + { + q: A @join__field(graph: A) + } + "#; + + let schema = Schema::parse(supergraph, "supergraph.graphql").unwrap(); + let ValidFederationSubgraphs { subgraphs } = super::extract_subgraphs_from_supergraph( + &FederationSchema::new(schema).unwrap(), + Some(true), + ) + .unwrap(); + + assert_eq!(subgraphs.len(), 3); + + let a = subgraphs.get("a").unwrap(); + // JS PORT NOTE: the original tests used the equivalent of `get_type`, + // so we have to be careful about using `get_interface` here. + assert!(a.schema.schema().get_interface("A").is_some()); + assert!(a.schema.schema().get_object("B").is_some()); + + let b = subgraphs.get("b").unwrap(); + assert!(b.schema.schema().get_interface("A").is_none()); + assert!(b.schema.schema().get_object("B").is_none()); + + let c = subgraphs.get("c").unwrap(); + assert!(c.schema.schema().get_interface("A").is_none()); + assert!(c.schema.schema().get_object("B").is_none()); + } + + #[test] + fn handles_types_having_no_fields_referenced_by_other_unions_in_a_subgraph_correctly() { + /* + * JS PORT NOTE: the original test used a Federation 1 supergraph. + * The following supergraph has been generated from: + + federation_version: =2.6.0 + subgraphs: + a: + routing_url: http://a + schema: + sdl: | + type Query { + q: A + } + + union A = B | C + + type B { + b: D @provides(fields: "d") + } + + type C { + c: D @provides(fields: "d") + } + + type D { + d: String + } + b: + routing_url: http://b + schema: + sdl: | + type D { + d: String + } + + * This tests is similar identical to 'handles types having no fields referenced by other objects in a subgraph correctly' + * but the reference to the type being removed is a union, one that should be fully removed. + */ + + let supergraph = r#" + schema + @link(url: "https://specs.apollo.dev/link/v1.0") + @link(url: "https://specs.apollo.dev/join/v0.3", for: EXECUTION) + { + query: Query + } + + directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE + + directive @join__field(graph: join__Graph, requires: join__FieldSet, provides: join__FieldSet, type: String, external: Boolean, override: String, usedOverridden: Boolean) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION + + directive @join__graph(name: String!, url: String!) on ENUM_VALUE + + directive @join__implements(graph: join__Graph!, interface: String!) repeatable on OBJECT | INTERFACE + + directive @join__type(graph: join__Graph!, key: join__FieldSet, extension: Boolean! = false, resolvable: Boolean! = true, isInterfaceObject: Boolean! = false) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR + + directive @join__unionMember(graph: join__Graph!, member: String!) repeatable on UNION + + directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA + + union A + @join__type(graph: A) + @join__unionMember(graph: A, member: "B") + @join__unionMember(graph: A, member: "C") + = B | C + + type B + @join__type(graph: A) + { + b: D + } + + type C + @join__type(graph: A) + { + c: D + } + + type D + @join__type(graph: A) + @join__type(graph: B) + { + d: String + } + + scalar join__FieldSet + + enum join__Graph { + A @join__graph(name: "a", url: "http://a") + B @join__graph(name: "b", url: "http://b") + } + + scalar link__Import + + enum link__Purpose { + """ + `SECURITY` features provide metadata necessary to securely resolve fields. + """ + SECURITY + + """ + `EXECUTION` features provide metadata necessary for operation execution. + """ + EXECUTION + } + + type Query + @join__type(graph: A) + @join__type(graph: B) + { + q: A @join__field(graph: A) + } + "#; + + let schema = Schema::parse(supergraph, "supergraph.graphql").unwrap(); + let ValidFederationSubgraphs { subgraphs } = super::extract_subgraphs_from_supergraph( + &FederationSchema::new(schema).unwrap(), + Some(true), + ) + .unwrap(); + + assert_eq!(subgraphs.len(), 2); + + let a = subgraphs.get("a").unwrap(); + // JS PORT NOTE: the original tests used the equivalent of `get_type`, + // so we have to be careful about using `get_union` here. + assert!(a.schema.schema().get_union("A").is_some()); + assert!(a.schema.schema().get_object("B").is_some()); + assert!(a.schema.schema().get_object("C").is_some()); + assert!(a.schema.schema().get_object("D").is_some()); + + let b = subgraphs.get("b").unwrap(); + assert!(b.schema.schema().get_union("A").is_none()); + assert!(b.schema.schema().get_object("B").is_none()); + assert!(b.schema.schema().get_object("C").is_none()); + assert!(b.schema.schema().get_object("D").is_some()); + } + + // JS PORT NOTE: the "handles types having only some of their fields removed in a subgraph correctly" + // test isn't relevant to Federation 2 supergraphs. Fed 1 supergraphs don't annotate all types with + // the associated subgraphs, so extraction sometimes required guessing about which types to bring + // into each subgraph. + + #[test] + fn handles_unions_types_having_no_members_in_a_subgraph_correctly() { + /* + * JS PORT NOTE: the original test used a Federation 1 supergraph. + * The following supergraph has been generated from: + + federation_version: =2.6.0 + subgraphs: + a: + routing_url: http://a + schema: + sdl: | + type Query { + q: A + } + + union A = B | C + + type B @key(fields: "b { d }") { + b: D + } + + type C @key(fields: "c { d }") { + c: D + } + + type D { + d: String + } + b: + routing_url: http://b + schema: + sdl: | + type D { + d: String + } + + * This tests is similar to the other test with unions, but because its members are enties, the + * members themself with have a join__owner, and that means the removal will hit a different + * code path (technically, the union A will be "removed" directly by `extractSubgraphsFromSupergraph` + * instead of being removed indirectly through the removal of its members). + */ + + let supergraph = r#" + schema + @link(url: "https://specs.apollo.dev/link/v1.0") + @link(url: "https://specs.apollo.dev/join/v0.3", for: EXECUTION) + { + query: Query + } + + directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE + + directive @join__field(graph: join__Graph, requires: join__FieldSet, provides: join__FieldSet, type: String, external: Boolean, override: String, usedOverridden: Boolean) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION + + directive @join__graph(name: String!, url: String!) on ENUM_VALUE + + directive @join__implements(graph: join__Graph!, interface: String!) repeatable on OBJECT | INTERFACE + + directive @join__type(graph: join__Graph!, key: join__FieldSet, extension: Boolean! = false, resolvable: Boolean! = true, isInterfaceObject: Boolean! = false) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR + + directive @join__unionMember(graph: join__Graph!, member: String!) repeatable on UNION + + directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA + + union A + @join__type(graph: A) + @join__unionMember(graph: A, member: "B") + @join__unionMember(graph: A, member: "C") + = B | C + + type B + @join__type(graph: A, key: "b { d }") + { + b: D + } + + type C + @join__type(graph: A, key: "c { d }") + { + c: D + } + + type D + @join__type(graph: A) + @join__type(graph: B) + { + d: String + } + + scalar join__FieldSet + + enum join__Graph { + A @join__graph(name: "a", url: "http://a") + B @join__graph(name: "b", url: "http://b") + } + + scalar link__Import + + enum link__Purpose { + """ + `SECURITY` features provide metadata necessary to securely resolve fields. + """ + SECURITY + + """ + `EXECUTION` features provide metadata necessary for operation execution. + """ + EXECUTION + } + + type Query + @join__type(graph: A) + @join__type(graph: B) + { + q: A @join__field(graph: A) + } + "#; + + let schema = Schema::parse(supergraph, "supergraph.graphql").unwrap(); + let ValidFederationSubgraphs { subgraphs } = super::extract_subgraphs_from_supergraph( + &FederationSchema::new(schema).unwrap(), + Some(true), + ) + .unwrap(); + + assert_eq!(subgraphs.len(), 2); + + let a = subgraphs.get("a").unwrap(); + // JS PORT NOTE: the original tests used the equivalent of `get_type`, + // so we have to be careful about using `get_union` here. + assert!(a.schema.schema().get_union("A").is_some()); + assert!(a.schema.schema().get_object("B").is_some()); + assert!(a.schema.schema().get_object("C").is_some()); + assert!(a.schema.schema().get_object("D").is_some()); + + let b = subgraphs.get("b").unwrap(); + assert!(b.schema.schema().get_union("A").is_none()); + assert!(b.schema.schema().get_object("B").is_none()); + assert!(b.schema.schema().get_object("C").is_none()); + assert!(b.schema.schema().get_object("D").is_some()); + } + + #[test] + fn preserves_default_values_of_input_object_fields() { + let supergraph = r#" + schema + @link(url: "https://specs.apollo.dev/link/v1.0") + @link(url: "https://specs.apollo.dev/join/v0.2", for: EXECUTION) + { + query: Query + } + + directive @join__field(graph: join__Graph!, requires: join__FieldSet, provides: join__FieldSet, type: String, external: Boolean, override: String, usedOverridden: Boolean) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION + + directive @join__graph(name: String!, url: String!) on ENUM_VALUE + + directive @join__implements(graph: join__Graph!, interface: String!) repeatable on OBJECT | INTERFACE + + directive @join__type(graph: join__Graph!, key: join__FieldSet, extension: Boolean! = false, resolvable: Boolean! = true) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR + + directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA + + input Input + @join__type(graph: SERVICE) + { + a: Int! = 1234 + } + + scalar join__FieldSet + + enum join__Graph { + SERVICE @join__graph(name: "service", url: "") + } + + scalar link__Import + + enum link__Purpose { + """ + `SECURITY` features provide metadata necessary to securely resolve fields. + """ + SECURITY + + """ + `EXECUTION` features provide metadata necessary for operation execution. + """ + EXECUTION + } + + type Query + @join__type(graph: SERVICE) + { + field(input: Input!): String + } + "#; + + let schema = Schema::parse(supergraph, "supergraph.graphql").unwrap(); + let ValidFederationSubgraphs { subgraphs } = super::extract_subgraphs_from_supergraph( + &FederationSchema::new(schema).unwrap(), + Some(true), + ) + .unwrap(); + + assert_eq!(subgraphs.len(), 1); + let subgraph = subgraphs.get("service").unwrap(); + let input_type = subgraph.schema.schema().get_input_object("Input").unwrap(); + let input_field_a = input_type + .fields + .iter() + .find(|(name, _)| name == &&name!("a")) + .unwrap(); + assert_eq!( + input_field_a.1.default_value.as_ref().unwrap().to_i32(), + Some(1234) + ); + } + + // JS PORT NOTE: the "throw meaningful error for invalid federation directive fieldSet" + // test checked an error condition that can appear only in a Federation 1 supergraph. + + // JS PORT NOTE: the "throw meaningful error for type erased from supergraph due to extending an entity without a key" + // test checked an error condition that can appear only in a Federation 1 supergraph. + + #[test] + fn types_that_are_empty_because_of_overridden_fields_are_erased() { + let supergraph = r#" + schema + @link(url: "https://specs.apollo.dev/link/v1.0") + @link(url: "https://specs.apollo.dev/join/v0.3", for: EXECUTION) + @link(url: "https://specs.apollo.dev/tag/v0.3") + { + query: Query + } + + directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE + + directive @join__field(graph: join__Graph, requires: join__FieldSet, provides: join__FieldSet, type: String, external: Boolean, override: String, usedOverridden: Boolean) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION + + directive @join__graph(name: String!, url: String!) on ENUM_VALUE + + directive @join__implements(graph: join__Graph!, interface: String!) repeatable on OBJECT | INTERFACE + + directive @join__type(graph: join__Graph!, key: join__FieldSet, extension: Boolean! = false, resolvable: Boolean! = true, isInterfaceObject: Boolean! = false) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR + + directive @join__unionMember(graph: join__Graph!, member: String!) repeatable on UNION + + directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA + + directive @tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA + input Input + @join__type(graph: B) + { + a: Int! = 1234 + } + + scalar join__FieldSet + + enum join__Graph { + A @join__graph(name: "a", url: "") + B @join__graph(name: "b", url: "") + } + + scalar link__Import + + enum link__Purpose { + """ + `SECURITY` features provide metadata necessary to securely resolve fields. + """ + SECURITY + + """ + `EXECUTION` features provide metadata necessary for operation execution. + """ + EXECUTION + } + + type Query + @join__type(graph: A) + { + field: String + } + + type User + @join__type(graph: A) + @join__type(graph: B) + { + foo: String @join__field(graph: A, override: "b") + + bar: String @join__field(graph: A) + + baz: String @join__field(graph: A) + } + "#; + + let schema = Schema::parse(supergraph, "supergraph.graphql").unwrap(); + let ValidFederationSubgraphs { subgraphs } = super::extract_subgraphs_from_supergraph( + &FederationSchema::new(schema).unwrap(), + Some(true), + ) + .unwrap(); + + let subgraph = subgraphs.get("a").unwrap(); + let user_type = subgraph.schema.schema().get_object("User"); + assert!(user_type.is_some()); + + let subgraph = subgraphs.get("b").unwrap(); + let user_type = subgraph.schema.schema().get_object("User"); + assert!(user_type.is_none()); + } +} diff --git a/apollo-federation/src/query_graph/graph_path.rs b/apollo-federation/src/query_graph/graph_path.rs new file mode 100644 index 0000000000..8127b3dfc8 --- /dev/null +++ b/apollo-federation/src/query_graph/graph_path.rs @@ -0,0 +1,3629 @@ +use std::cmp::Ordering; +use std::collections::BinaryHeap; +use std::collections::HashSet; +use std::fmt::Display; +use std::fmt::Formatter; +use std::fmt::Write; +use std::hash::Hash; +use std::ops::Deref; +use std::sync::atomic; +use std::sync::Arc; + +use apollo_compiler::ast::Value; +use apollo_compiler::executable::DirectiveList; +use apollo_compiler::schema::ExtendedType; +use apollo_compiler::schema::Name; +use apollo_compiler::NodeStr; +use indexmap::IndexMap; +use indexmap::IndexSet; +use petgraph::graph::EdgeIndex; +use petgraph::graph::NodeIndex; +use petgraph::visit::EdgeRef; + +use crate::error::FederationError; +use crate::indented_display::write_indented_lines; +use crate::indented_display::State as IndentedFormatter; +use crate::is_leaf_type; +use crate::link::federation_spec_definition::get_federation_spec_definition_from_subgraph; +use crate::link::graphql_definition::BooleanOrVariable; +use crate::link::graphql_definition::DeferDirectiveArguments; +use crate::link::graphql_definition::OperationConditional; +use crate::link::graphql_definition::OperationConditionalKind; +use crate::query_graph::condition_resolver::ConditionResolution; +use crate::query_graph::condition_resolver::ConditionResolver; +use crate::query_graph::condition_resolver::UnsatisfiedConditionReason; +use crate::query_graph::path_tree::OpPathTree; +use crate::query_graph::QueryGraph; +use crate::query_graph::QueryGraphEdgeTransition; +use crate::query_graph::QueryGraphNodeType; +use crate::query_plan::operation::Field; +use crate::query_plan::operation::FieldData; +use crate::query_plan::operation::HasSelectionKey; +use crate::query_plan::operation::InlineFragment; +use crate::query_plan::operation::InlineFragmentData; +use crate::query_plan::operation::RebaseErrorHandlingOption; +use crate::query_plan::operation::SelectionId; +use crate::query_plan::operation::SelectionKey; +use crate::query_plan::operation::SelectionSet; +use crate::query_plan::FetchDataPathElement; +use crate::query_plan::QueryPathElement; +use crate::query_plan::QueryPlanCost; +use crate::schema::position::AbstractTypeDefinitionPosition; +use crate::schema::position::CompositeTypeDefinitionPosition; +use crate::schema::position::InterfaceFieldDefinitionPosition; +use crate::schema::position::ObjectTypeDefinitionPosition; +use crate::schema::position::OutputTypeDefinitionPosition; +use crate::schema::position::TypeDefinitionPosition; +use crate::schema::ValidFederationSchema; + +/// An immutable path in a query graph. +/// +/// A "path" here is mostly understood in the graph-theoretical sense of the term, i.e. as "a +/// connected series of edges"; a `GraphPath` is generated by traversing a query graph. +/// +/// However, as query graph edges may have conditions, a `GraphPath` also records, for each edge it +/// is composed of, the set of paths (an `OpPathTree` in practice) that were taken to fulfill each +/// edge's conditions (when an edge has one). +/// +/// Additionally, for each edge of the path, a `GraphPath` records the "trigger" that made the +/// traversal take that edge. In practice, the "trigger" can be seen as a way to decorate a path +/// with some additional metadata for each element of the path. In practice, that trigger is used in +/// 2 main ways (corresponding to our 2 main query graph traversals): +/// - For composition validation, the traversal of the federated query graph is driven by other +/// transitions into the supergraph API query graph (essentially, composition validation is about +/// finding, for every path in supergraph API query graph, a "matching" traversal of the federated +/// query graph). In that case, for the graph paths we build on the federated query graph, the +/// "trigger" will be one of the edge transitions from the supergraph API query graph (which, +/// granted, will be fairly similar to the one of the edge we're taking in the federated query +/// graph; in practice, triggers are more useful in the query planning case). +/// - For query planning, the traversal of the federated query graph is driven by the elements of +/// the query we are planning. Which means that the "trigger" for taking an edge in this case will +/// be an operation element (or `None`). See the specialized `OpGraphPath` that is defined for this +/// use case. +/// +/// Lastly, some `GraphPath`s can actually encode `None` edges: this is used during query planning +/// in the (rare) case where the query we plan for has an inline fragment spread without type +/// condition (or a "useless" one, i.e. one that doesn't restrict the possible types anymore than +/// they already were) but with some directives. In that case, we want to preserve the information +/// about the directive (to properly rebuild query plans later) but it doesn't correspond to taking +/// any edges, so we add a `None` edge and use the trigger to store the fragment spread. +/// +/// Regarding type parameters: +/// - `TTrigger`: The type of the path's "triggers", metadata that can associated to each element +/// of the path (see above for more details). +/// - `TEdge`: The type of the edge. Either `Option` (meaning that the path may have a +/// `None` edge) or `never` (the path cannot have `None` edges). +// PORT_NOTE: The JS codebase also parameterized whether the head of the path was a root node, but +// in the Rust code we don't have a distinguished type for that case. We instead check this at +// runtime (at the callsites that require root nodes). This means the `RootPath` type in the +// JS codebase is replaced with this one. +#[derive(Clone)] +pub(crate) struct GraphPath +where + TTrigger: Eq + Hash, + Arc: Into, + TEdge: Copy + Into>, + EdgeIndex: Into, +{ + /// The query graph of which this is a path. + graph: Arc, + /// The node at which the path starts. This should be the head of the first non-`None` edge in + /// the path if such edge exists, but if there are only `None` edges (or if there are zero + /// edges), this will still exist (and the head and tail of the path will be the same). + head: NodeIndex, + /// The node at which the path stops. This should be the tail of the last non-`None` edge in the + /// path if such edge exists, but if there are only `None` edges (or if there are zero edges), + /// this will still exist (and the head and tail of the path will be the same). + pub(crate) tail: NodeIndex, + /// The edges composing the path. + edges: Vec, + /// The triggers associated to each edge in the path. + edge_triggers: Vec>, + /// For each edge in the path, if the edge has conditions, the set of paths that fulfill that + /// condition. + /// + /// Note that no matter which kind of traversal we are doing (composition or query planning), + /// fulfilling the conditions is always driven by the conditions themselves, and since + /// conditions are a GraphQL result set, the resulting set of paths are an `OpGraphPath` (and + /// since they start at the edge's head node, we use the `OpPathTree` representation for that + /// set of paths). + edge_conditions: Vec>>, + /// Information about the last subgraph-entering edge in this path, which is used to eliminate + /// some non-optimal paths. (This is reset when encountering a `@defer` application.) + last_subgraph_entering_edge_info: Option, + /// As part of an optimization, we keep track of when one path "overrides" other paths by + /// creating an ID, and storing that ID in the paths to track the "overrides" relationship (not + /// to be confused with the `@override` directive, which is completely separate). + /// + /// This array stores the IDs associated with this path. + own_path_ids: Arc>, + /// This array stores the IDs of paths that override this one. (See docs for `own_path_ids` for + /// more info). + overriding_path_ids: Arc>, + /// Names of all the possible runtime types the tail of the path can be. + runtime_types_of_tail: Arc>, + /// If the last edge in the `edges` array was a `DownCast` transition, then the runtime types + /// before that edge. + runtime_types_before_tail_if_last_is_cast: Option>>, + /// If the trigger of the last edge in the `edges` array was an operation element with a + /// `@defer` application, then the arguments of that application. + defer_on_tail: Option, +} + +impl std::fmt::Debug for GraphPath +where + TTrigger: Eq + Hash, + Arc: Into, + TEdge: Copy + Into>, + EdgeIndex: Into, + // In addition to the bounds of the GraphPath struct, also require Debug: + TTrigger: std::fmt::Debug, + TEdge: std::fmt::Debug, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let Self { + graph: _, // skip + head, + tail, + edges, + edge_triggers, + edge_conditions, + last_subgraph_entering_edge_info, + own_path_ids, + overriding_path_ids, + runtime_types_of_tail, + runtime_types_before_tail_if_last_is_cast, + defer_on_tail, + } = self; + + f.debug_struct("GraphPath") + .field("head", head) + .field("tail", tail) + .field("edges", edges) + .field("edge_triggers", edge_triggers) + .field("edge_conditions", edge_conditions) + .field( + "last_subgraph_entering_edge_info", + last_subgraph_entering_edge_info, + ) + .field("own_path_ids", own_path_ids) + .field("overriding_path_ids", overriding_path_ids) + .field("runtime_types_of_tail", runtime_types_of_tail) + .field( + "runtime_types_before_tail_if_last_is_cast", + runtime_types_before_tail_if_last_is_cast, + ) + .field("defer_on_tail", defer_on_tail) + .finish_non_exhaustive() + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, derive_more::From)] +pub(crate) enum GraphPathTrigger { + Op(Arc), + Transition(Arc), +} + +#[derive(Debug, Clone)] +pub(crate) struct SubgraphEnteringEdgeInfo { + /// The index within the `edges` array. + index: usize, + /// The cost of resolving the conditions for this edge. + conditions_cost: QueryPlanCost, +} + +/// Wrapper for an override ID, which indicates a relationship between a group of `OpGraphPath`s +/// where one "overrides" the others in the group. +/// +/// Note that we shouldn't add `derive(Serialize, Deserialize)` to this without changing the types +/// to be something like UUIDs. +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +pub(crate) struct OverrideId(usize); + +/// Global storage for the counter used to allocate `OverrideId`s. +static NEXT_OVERRIDE_ID: atomic::AtomicUsize = atomic::AtomicUsize::new(1); + +impl OverrideId { + fn new() -> Self { + // atomically increment global counter + Self(NEXT_OVERRIDE_ID.fetch_add(1, atomic::Ordering::AcqRel)) + } +} + +/// The item type for [`GraphPath::iter`] +pub(crate) type GraphPathItem<'path, TTrigger, TEdge> = + (TEdge, &'path Arc, &'path Option>); + +/// A `GraphPath` whose triggers are operation elements (essentially meaning that the path has been +/// guided by a GraphQL operation). +// PORT_NOTE: As noted in the docs for `GraphPath`, we omit a type parameter for the root node, +// whose constraint is instead checked at runtime. This means the `OpRootPath` type in the JS +// codebase is replaced with this one. +pub(crate) type OpGraphPath = GraphPath>; + +#[derive(Debug, Clone, PartialEq, Eq, Hash, derive_more::From)] +pub(crate) enum OpGraphPathTrigger { + OpPathElement(OpPathElement), + Context(OpGraphPathContext), +} + +impl Display for OpGraphPathTrigger { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + OpGraphPathTrigger::OpPathElement(ele) => ele.fmt(f), + OpGraphPathTrigger::Context(ctx) => ctx.fmt(f), + } + } +} + +/// A path of operation elements within a GraphQL operation. +#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)] +pub(crate) struct OpPath(pub(crate) Vec>); + +impl Deref for OpPath { + type Target = [Arc]; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl std::fmt::Display for OpPath { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + for (i, element) in self.0.iter().enumerate() { + if i > 0 { + write!(f, "::")?; + } + match element.deref() { + OpPathElement::Field(field) => write!(f, "{field}")?, + OpPathElement::InlineFragment(fragment) => write!(f, "{fragment}")?, + } + } + Ok(()) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, derive_more::From)] +pub(crate) enum OpPathElement { + Field(Field), + InlineFragment(InlineFragment), +} + +impl HasSelectionKey for OpPathElement { + fn key(&self) -> SelectionKey { + match self { + OpPathElement::Field(field) => field.key(), + OpPathElement::InlineFragment(fragment) => fragment.key(), + } + } +} + +impl OpPathElement { + pub(crate) fn directives(&self) -> &Arc { + match self { + OpPathElement::Field(field) => &field.data().directives, + OpPathElement::InlineFragment(inline_fragment) => &inline_fragment.data().directives, + } + } + + pub(crate) fn schema(&self) -> &ValidFederationSchema { + match self { + OpPathElement::Field(field) => field.schema(), + OpPathElement::InlineFragment(fragment) => fragment.schema(), + } + } + + pub(crate) fn is_terminal(&self) -> Result { + match self { + OpPathElement::Field(field) => field.data().is_leaf(), + OpPathElement::InlineFragment(_) => Ok(false), + } + } + + pub(crate) fn sibling_typename(&self) -> Option<&Name> { + match self { + OpPathElement::Field(field) => field.sibling_typename(), + OpPathElement::InlineFragment(_) => None, + } + } + + pub(crate) fn parent_type_position(&self) -> CompositeTypeDefinitionPosition { + match self { + OpPathElement::Field(field) => field.data().field_position.parent(), + OpPathElement::InlineFragment(inline) => inline.data().parent_type_position.clone(), + } + } + + pub(crate) fn extract_operation_conditionals( + &self, + ) -> Result, FederationError> { + let mut conditionals = vec![]; + // PORT_NOTE: We explicitly use the order `Skip` and `Include` here, to align with the order + // used by the JS codebase. + for kind in [ + OperationConditionalKind::Skip, + OperationConditionalKind::Include, + ] { + let directive_name: &'static str = (&kind).into(); + if let Some(application) = self.directives().get(directive_name) { + let Some(arg) = application.argument_by_name("if") else { + return Err(FederationError::internal(format!( + "@{} missing required argument \"if\"", + directive_name + ))); + }; + let value = match arg.deref() { + Value::Variable(variable_name) => { + BooleanOrVariable::Variable(variable_name.clone()) + } + Value::Boolean(boolean) => BooleanOrVariable::Boolean(*boolean), + _ => { + return Err(FederationError::internal(format!( + "@{} has invalid value {} for argument \"if\"", + directive_name, + arg.serialize().no_indent() + ))); + } + }; + conditionals.push(OperationConditional { kind, value }) + } + } + Ok(conditionals) + } + + pub(crate) fn with_updated_directives(&self, directives: DirectiveList) -> OpPathElement { + match self { + OpPathElement::Field(field) => { + OpPathElement::Field(field.with_updated_directives(directives)) + } + OpPathElement::InlineFragment(inline_fragment) => { + OpPathElement::InlineFragment(inline_fragment.with_updated_directives(directives)) + } + } + } + + pub(crate) fn as_path_element(&self) -> Option { + match self { + OpPathElement::Field(field) => Some(field.as_path_element()), + OpPathElement::InlineFragment(inline_fragment) => inline_fragment.as_path_element(), + } + } + + pub(crate) fn defer_directive_args(&self) -> Option { + match self { + OpPathElement::Field(_) => None, // @defer cannot be on field at the moment + OpPathElement::InlineFragment(inline_fragment) => inline_fragment + .data() + .defer_directive_arguments() + .ok() + .flatten(), + } + } + + /// Returns this fragment element but with any @defer directive on it removed. + /// + /// This method will return `None` if, upon removing @defer, the fragment has no conditions nor + /// any remaining applied directives (meaning that it carries no information whatsoever and can be + /// ignored). + pub(crate) fn without_defer(&self) -> Option { + match self { + Self::Field(_) => Some(self.clone()), // unchanged + Self::InlineFragment(inline_fragment) => { + let updated_directives: DirectiveList = inline_fragment + .data() + .directives + .get_all("defer") + .cloned() + .collect(); + if inline_fragment.data().type_condition_position.is_none() + && updated_directives.is_empty() + { + return None; + } + if inline_fragment.data().directives.len() == updated_directives.len() { + Some(self.clone()) + } else { + // PORT_NOTE: We won't need to port `this.copyAttachementsTo(updated);` line here + // since `with_updated_directives` clones the whole `self` and thus sibling + // type names should be copied as well. + Some(self.with_updated_directives(updated_directives)) + } + } + } + } + + pub(crate) fn rebase_on( + &self, + parent_type: &CompositeTypeDefinitionPosition, + schema: &ValidFederationSchema, + error_handling: RebaseErrorHandlingOption, + ) -> Result, FederationError> { + match self { + OpPathElement::Field(field) => field + .rebase_on(parent_type, schema, error_handling) + .map(|val| val.map(Into::into)), + OpPathElement::InlineFragment(inline) => inline + .rebase_on(parent_type, schema, error_handling) + .map(|val| val.map(Into::into)), + } + } +} + +impl Display for OpPathElement { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + OpPathElement::Field(field) => field.fmt(f), + OpPathElement::InlineFragment(inline_fragment) => inline_fragment.fmt(f), + } + } +} + +impl From for OpGraphPathTrigger { + fn from(value: Field) -> Self { + OpPathElement::from(value).into() + } +} + +impl From for OpGraphPathTrigger { + fn from(value: InlineFragment) -> Self { + OpPathElement::from(value).into() + } +} + +/// Records, as we walk a path within a GraphQL operation, important directives encountered +/// (currently `@include` and `@skip` with their conditions). +#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)] +pub(crate) struct OpGraphPathContext { + /// A list of conditionals (e.g. `[{ kind: Include, value: true}, { kind: Skip, value: $foo }]`) + /// in the reverse order in which they were applied (so the first element is the inner-most + /// applied include/skip). + conditionals: Arc>>, +} + +impl OpGraphPathContext { + pub(crate) fn with_context_of( + &self, + operation_element: &OpPathElement, + ) -> Result { + let mut new_context = self.clone(); + if operation_element.directives().is_empty() { + return Ok(new_context); + } + + let new_conditionals = operation_element.extract_operation_conditionals()?; + if !new_conditionals.is_empty() { + Arc::make_mut(&mut new_context.conditionals) + .extend(new_conditionals.into_iter().map(Arc::new)); + } + Ok(new_context) + } + + pub(crate) fn is_empty(&self) -> bool { + self.conditionals.is_empty() + } + + pub(crate) fn iter(&self) -> impl Iterator { + self.conditionals.iter().map(|x| x.as_ref()) + } +} + +impl Display for OpGraphPathContext { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "[")?; + let mut iter = self.conditionals.iter(); + if let Some(cond) = iter.next() { + write!(f, "@{}(if: {})", cond.kind, cond.value)?; + iter.try_for_each(|cond| write!(f, ", @{}(if: {})", cond.kind, cond.value))?; + } + write!(f, "]") + } +} + +/// A vector of graph paths that are being considered simultaneously by the query planner as an +/// option for a path within a GraphQL operation. These arise since the edge to take in a query +/// graph may depend on outcomes that are only known at query plan execution time, and we account +/// for this by splitting a path into multiple paths (one for each possible outcome). The common +/// example is abstract types, where we may end up taking a different edge depending on the runtime +/// type (e.g. during type explosion). +#[derive(Clone)] +pub(crate) struct SimultaneousPaths(pub(crate) Vec>); + +impl SimultaneousPaths { + pub(crate) fn fmt_indented(&self, f: &mut IndentedFormatter) -> std::fmt::Result { + match self.0.as_slice() { + [] => f.write(""), + + [first] => f.write_fmt(format_args!("{{ {first} }}")), + + _ => { + f.write("{")?; + write_indented_lines(f, &self.0, |f, elem| f.write(elem))?; + f.write("}") + } + } + } +} + +impl std::fmt::Debug for SimultaneousPaths { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_list() + .entries(self.0.iter().map(ToString::to_string)) + .finish() + } +} + +impl std::fmt::Display for SimultaneousPaths { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.fmt_indented(&mut IndentedFormatter::new(f)) + } +} + +/// One of the options for an `OpenBranch` (see the documentation of that struct for details). This +/// includes the simultaneous paths we are traversing for the option, along with metadata about the +/// traversal. +// PORT_NOTE: The JS codebase stored a `ConditionResolver` callback here, but it was the same for +// a given traversal (and cached resolution across the traversal), so we accordingly store it in +// `QueryPlanTraversal` and pass it down when needed instead. +#[derive(Debug, Clone)] +pub(crate) struct SimultaneousPathsWithLazyIndirectPaths { + pub(crate) paths: SimultaneousPaths, + pub(crate) context: OpGraphPathContext, + pub(crate) excluded_destinations: ExcludedDestinations, + pub(crate) excluded_conditions: ExcludedConditions, + pub(crate) lazily_computed_indirect_paths: Vec>, +} + +/// A "set" of excluded destinations (i.e. subgraph names). Note that we use a `Vec` instead of set +/// because this is used in pretty hot paths (the whole path computation is CPU intensive) and will +/// basically always be tiny (it's bounded by the number of distinct key on a given type, so usually +/// 2-3 max; even in completely unrealistic cases, it's hard bounded by the number of subgraphs), so +/// a `Vec` is going to perform a lot better than `IndexSet` in practice. +#[derive(Debug, Clone)] +pub(crate) struct ExcludedDestinations(Arc>); + +impl ExcludedDestinations { + fn is_excluded(&self, destination: &NodeStr) -> bool { + self.0.contains(destination) + } + + fn add_excluded(&self, destination: NodeStr) -> Self { + if !self.is_excluded(&destination) { + let mut new = self.0.as_ref().clone(); + new.push(destination); + Self(Arc::new(new)) + } else { + self.clone() + } + } +} + +impl PartialEq for ExcludedDestinations { + /// See if two `ExcludedDestinations` have the same set of values, regardless of their ordering. + fn eq(&self, other: &ExcludedDestinations) -> bool { + self.0.len() == other.0.len() && self.0.iter().all(|x| other.0.contains(x)) + } +} + +impl Default for ExcludedDestinations { + fn default() -> Self { + ExcludedDestinations(Arc::new(vec![])) + } +} + +#[derive(Debug, Clone)] +pub(crate) struct ExcludedConditions(Arc>>); + +impl ExcludedConditions { + pub(crate) fn is_empty(&self) -> bool { + self.0.is_empty() + } + + fn is_excluded(&self, condition: Option<&Arc>) -> bool { + let Some(condition) = condition else { + return false; + }; + self.0.contains(condition) + } + + /// Immutable version of `push`. + pub(crate) fn add_item(&self, value: &SelectionSet) -> ExcludedConditions { + let mut result = self.0.as_ref().clone(); + result.push(value.clone().into()); + ExcludedConditions(Arc::new(result)) + } +} + +impl Default for ExcludedConditions { + fn default() -> Self { + ExcludedConditions(Arc::new(vec![])) + } +} + +#[derive(Clone)] +pub(crate) struct IndirectPaths +where + TTrigger: Eq + Hash, + Arc: Into, + TEdge: Copy + Into>, + EdgeIndex: Into, +{ + paths: Arc>>>, + dead_ends: Arc, +} + +type OpIndirectPaths = IndirectPaths>; + +impl std::fmt::Debug for OpIndirectPaths { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("OpIndirectPaths") + .field( + "paths", + &self + .paths + .iter() + .map(ToString::to_string) + .collect::>(), + ) + .field("dead_ends", &self.dead_ends) + .finish() + } +} + +impl OpIndirectPaths { + /// When `self` is just-computed indirect paths and given a field that we're trying to advance + /// after those paths, this method filters any paths that should not be considered. + /// + /// Currently, this handles the case where the key used at the end of the indirect path contains + /// (at top level) the field being queried. Or to make this more concrete, if we're trying to + /// collect field `id`, and the path's last edge was using key `id`, then we can ignore that + /// path because this implies that there is a way to fetch `id` "some other way". + pub(crate) fn filter_non_collecting_paths_for_field( + &self, + field: &Field, + ) -> Result { + // We only handle leaves; Things are more complex for non-leaves. + if !field.data().is_leaf()? { + return Ok(self.clone()); + } + + let mut filtered = vec![]; + for path in self.paths.iter() { + if let Some(Some(last_edge)) = path.edges.last() { + let last_edge_weight = path.graph.edge_weight(*last_edge)?; + if matches!( + last_edge_weight.transition, + QueryGraphEdgeTransition::KeyResolution + ) { + if let Some(conditions) = &last_edge_weight.conditions { + if conditions.contains_top_level_field(field)? { + continue; + } + } + } + } + filtered.push(path.clone()) + } + Ok(if filtered.len() == self.paths.len() { + self.clone() + } else { + OpIndirectPaths { + paths: Arc::new(filtered), + dead_ends: self.dead_ends.clone(), + } + }) + } +} + +#[derive(Debug, Clone)] +struct Unadvanceables(Vec); + +impl Display for Unadvanceables { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_char('[')?; + let mut unadvanceables = self.0.iter(); + if let Some(unadvanceable) = unadvanceables.next() { + unadvanceable.fmt(f)?; + for unadvanceable in unadvanceables { + f.write_str(", ")?; + unadvanceable.fmt(f)?; + } + } + f.write_char(']') + } +} + +#[derive(Debug, Clone)] +struct Unadvanceable { + reason: UnadvanceableReason, + from_subgraph: NodeStr, + to_subgraph: NodeStr, + details: String, +} + +impl Display for Unadvanceable { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "[{}]({}->{}) {}", + self.reason, self.from_subgraph, self.to_subgraph, self.details + ) + } +} + +#[derive(Debug, Clone, strum_macros::Display)] +enum UnadvanceableReason { + UnsatisfiableKeyCondition, + UnsatisfiableRequiresCondition, + UnresolvableInterfaceObject, + NoMatchingTransition, + UnreachableType, + IgnoredIndirectPath, +} + +/// One of the options for a `ClosedBranch` (see the documentation of that struct for details). Note +/// there is an optimization here, in that if some ending section of the path within the GraphQL +/// operation can be satisfied by a query to a single subgraph, then we just record that selection +/// set, and the `SimultaneousPaths` ends at the node at which that query is made instead of a node +/// for the leaf field. The selection set gets copied "as-is" into the `FetchNode`, and also avoids +/// extra `GraphPath` creation and work during `PathTree` merging. +#[derive(Debug)] +pub(crate) struct ClosedPath { + pub(crate) paths: SimultaneousPaths, + pub(crate) selection_set: Option>, +} + +impl ClosedPath { + pub(crate) fn flatten( + &self, + ) -> impl Iterator>)> { + self.paths + .0 + .iter() + .map(|path| (path.as_ref(), self.selection_set.as_ref())) + } +} + +impl std::fmt::Display for ClosedPath { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + if let Some(ref selection_set) = self.selection_set { + write!(f, "{} -> {}", self.paths, selection_set) + } else { + write!(f, "{}", self.paths) + } + } +} + +/// A list of the options generated during query planning for a specific "closed branch", which is a +/// full/closed path in a GraphQL operation (i.e. one that ends in a leaf field). +#[derive(Debug)] +pub(crate) struct ClosedBranch(pub(crate) Vec>); + +/// A list of the options generated during query planning for a specific "open branch", which is a +/// partial/open path in a GraphQL operation (i.e. one that does not end in a leaf field). +#[derive(Debug)] +pub(crate) struct OpenBranch(pub(crate) Vec); + +impl GraphPath +where + TTrigger: Eq + Hash, + Arc: Into, + TEdge: Copy + Into>, + EdgeIndex: Into, +{ + pub(crate) fn new(graph: Arc, head: NodeIndex) -> Result { + let mut path = Self { + graph, + head, + tail: head, + edges: vec![], + edge_triggers: vec![], + edge_conditions: vec![], + last_subgraph_entering_edge_info: None, + own_path_ids: Arc::new(IndexSet::new()), + overriding_path_ids: Arc::new(IndexSet::new()), + runtime_types_of_tail: Arc::new(IndexSet::new()), + runtime_types_before_tail_if_last_is_cast: None, + defer_on_tail: None, + }; + path.runtime_types_of_tail = Arc::new(path.head_possible_runtime_types()?); + Ok(path) + } + + fn head_possible_runtime_types( + &self, + ) -> Result, FederationError> { + let head_weight = self.graph.node_weight(self.head)?; + Ok(match &head_weight.type_ { + QueryGraphNodeType::SchemaType(head_type_pos) => { + let head_type_pos: CompositeTypeDefinitionPosition = + head_type_pos.clone().try_into()?; + self.graph + .schema_by_source(&head_weight.source)? + .possible_runtime_types(head_type_pos)? + } + QueryGraphNodeType::FederatedRootType(_) => IndexSet::new(), + }) + } + + pub(crate) fn add( + &self, + trigger: TTrigger, + edge: TEdge, + condition_resolution: ConditionResolution, + defer: Option, + ) -> Result { + let ConditionResolution::Satisfied { + path_tree: condition_path_tree, + cost: condition_cost, + } = condition_resolution + else { + return Err(FederationError::internal( + "Cannot add an edge to a path if its conditions cannot be satisfied", + )); + }; + + let mut edges = self.edges.clone(); + let mut edge_triggers = self.edge_triggers.clone(); + let mut edge_conditions = self.edge_conditions.clone(); + + let Some(new_edge) = edge.into() else { + edges.push(edge); + edge_triggers.push(Arc::new(trigger)); + edge_conditions.push(condition_path_tree); + return Ok(GraphPath { + graph: self.graph.clone(), + head: self.head, + tail: self.tail, + edges, + edge_triggers, + edge_conditions, + // We clear `last_subgraph_entering_edge_info` as we enter a `@defer`. That is + // because `last_subgraph_entering_edge_info` is used to eliminate some non-optimal + // paths, but we don't want those optimizations to bypass a `@defer` application. + last_subgraph_entering_edge_info: if defer.is_some() { + None + } else { + self.last_subgraph_entering_edge_info.clone() + }, + own_path_ids: self.own_path_ids.clone(), + overriding_path_ids: self.overriding_path_ids.clone(), + runtime_types_of_tail: Arc::new( + self.graph + .advance_possible_runtime_types(&self.runtime_types_of_tail, None)?, + ), + runtime_types_before_tail_if_last_is_cast: None, + defer_on_tail: defer, + }); + }; + + let (edge_head, edge_tail) = self.graph.edge_endpoints(new_edge)?; + let edge_weight = self.graph.edge_weight(new_edge)?; + let tail_weight = self.graph.node_weight(self.tail)?; + if self.tail != edge_head { + return Err(FederationError::internal(format!( + "Cannot add edge {} to path ending at {}", + edge_weight, tail_weight + ))); + } + if let Some(path_tree) = &condition_path_tree { + if edge_weight.conditions.is_none() { + return Err(FederationError::internal(format!( + "Unexpectedly got conditions paths {} for edge {} without conditions", + path_tree, edge_weight, + ))); + } + } + + if matches!( + edge_weight.transition, + QueryGraphEdgeTransition::Downcast { .. } + ) { + if let Some(Some(last_edge)) = self.edges.last().map(|e| (*e).into()) { + let Some(last_edge_trigger) = self.edge_triggers.last() else { + return Err(FederationError::internal( + "Could not find corresponding trigger for edge", + )); + }; + if let GraphPathTrigger::Op(last_operation_element) = + last_edge_trigger.clone().into() + { + if let OpGraphPathTrigger::OpPathElement(OpPathElement::InlineFragment( + last_operation_element, + )) = last_operation_element.as_ref() + { + if last_operation_element.data().directives.is_empty() { + // This mean we have 2 typecasts back-to-back, and that means the + // previous operation element might not be useful on this path. More + // precisely, the previous typecast was only useful if it restricted the + // possible runtime types of the type on which it applied more than the + // current typecast does (but note that if the previous typecast had + // directives, we keep it no matter what in case those directives are + // important). + // + // That is, we're in the case where we have (somewhere potentially deep + // in a query): + // f { # field 'f' of type A + // ... on B { + // ... on C { + // # more stuff + // } + // } + // } + // If the intersection of A and C is non empty and included (or equal) + // to the intersection of A and B, then there is no reason to have + // `... on B` at all because: + // 1. you can do `... on C` on `f` directly since the intersection of A + // and C is non-empty. + // 2. `... on C` restricts strictly more than `... on B` and so the + // latter can't impact the result. + // So if we detect that we're in that situation, we remove the + // `... on B` (but note that this is an optimization, keeping `... on B` + // wouldn't be incorrect, just useless). + let Some(runtime_types_before_tail) = + &self.runtime_types_before_tail_if_last_is_cast + else { + return Err(FederationError::internal( + "Could not find runtime types of path prior to inline fragment", + )); + }; + let new_runtime_types_of_tail = + self.graph.advance_possible_runtime_types( + runtime_types_before_tail, + Some(new_edge), + )?; + if !new_runtime_types_of_tail.is_empty() + && new_runtime_types_of_tail.is_subset(&self.runtime_types_of_tail) + { + // Note that `edge` starts at the node we wish to eliminate from the + // path. So we need to replace it with the edge going directly from + // the previous node to the new tail for this path. + // + // PORT_NOTE: The JS codebase has a bug where it doesn't check that + // the searched edges are downcast edges. We fix that here. + let (last_edge_head, _) = self.graph.edge_endpoints(last_edge)?; + let edge_tail_weight = self.graph.node_weight(edge_tail)?; + let mut new_edge = None; + for new_edge_ref in self.graph.out_edges(last_edge_head) { + if !matches!( + new_edge_ref.weight().transition, + QueryGraphEdgeTransition::Downcast { .. } + ) { + continue; + } + if self.graph.node_weight(new_edge_ref.target())?.type_ + == edge_tail_weight.type_ + { + new_edge = Some(new_edge_ref.id()); + break; + } + } + if let Some(new_edge) = new_edge { + // We replace the previous operation element with this one. + edges.pop(); + edge_triggers.pop(); + edge_conditions.pop(); + edges.push(new_edge.into()); + edge_triggers.push(Arc::new(trigger)); + edge_conditions.push(condition_path_tree); + return Ok(GraphPath { + graph: self.graph.clone(), + head: self.head, + tail: edge_tail, + edges, + edge_triggers, + edge_conditions, + last_subgraph_entering_edge_info: self + .last_subgraph_entering_edge_info + .clone(), + own_path_ids: self.own_path_ids.clone(), + overriding_path_ids: self.overriding_path_ids.clone(), + runtime_types_of_tail: Arc::new(new_runtime_types_of_tail), + runtime_types_before_tail_if_last_is_cast: self + .runtime_types_before_tail_if_last_is_cast + .clone(), + // We know the edge is a `DownCast`, so if there is no new + // `@defer` taking precedence, we just inherit the prior + // version. + defer_on_tail: if defer.is_some() { + defer + } else { + self.defer_on_tail.clone() + }, + }); + } + } + } + } + } + } + } + + if matches!( + edge_weight.transition, + QueryGraphEdgeTransition::KeyResolution { .. } + ) { + // We're adding a `@key` edge. If the last edge to that point is an `@interfaceObject` + // fake downcast, and if our destination type is not an `@interfaceObject` itself, then + // we can eliminate that last edge as it does nothing useful (also, it has conditions + // and we don't need/want the `@key` we're following to depend on those conditions, + // since it doesn't have to). + let edge_tail_weight = self.graph.node_weight(edge_tail)?; + if self.last_edge_is_interface_object_fake_down_cast()? + && matches!( + edge_tail_weight.type_, + QueryGraphNodeType::SchemaType(OutputTypeDefinitionPosition::Interface(_)) + ) + { + // We replace the previous operation element with this one. + edges.pop(); + edge_triggers.pop(); + edge_conditions.pop(); + edges.push(edge); + edge_triggers.push(Arc::new(trigger)); + edge_conditions.push(condition_path_tree); + return Ok(GraphPath { + graph: self.graph.clone(), + head: self.head, + tail: edge_tail, + edges, + edge_triggers, + edge_conditions, + // Again, we don't want to set `last_subgraph_entering_edge_info` if we're + // entering a `@defer` (see above). + // + // PORT_NOTE: In the JS codebase, the information for the last subgraph-entering + // is set incorrectly, in that the index is off by one. We fix that bug here. + last_subgraph_entering_edge_info: if defer.is_none() + && self.graph.is_cross_subgraph_edge(new_edge)? + { + Some(SubgraphEnteringEdgeInfo { + index: self.edges.len() - 1, + conditions_cost: condition_cost, + }) + } else { + None + }, + own_path_ids: self.own_path_ids.clone(), + overriding_path_ids: self.overriding_path_ids.clone(), + runtime_types_of_tail: Arc::new(self.graph.advance_possible_runtime_types( + &self.runtime_types_of_tail, + Some(new_edge), + )?), + // We know last edge is not a cast. + runtime_types_before_tail_if_last_is_cast: None, + defer_on_tail: defer, + }); + } + } + + edges.push(edge); + edge_triggers.push(Arc::new(trigger)); + edge_conditions.push(condition_path_tree); + Ok(GraphPath { + graph: self.graph.clone(), + head: self.head, + tail: edge_tail, + edges, + edge_triggers, + edge_conditions, + // Again, we don't want to set `last_subgraph_entering_edge_info` if we're entering a + // `@defer` (see above). + last_subgraph_entering_edge_info: if defer.is_none() + && self.graph.is_cross_subgraph_edge(new_edge)? + { + Some(SubgraphEnteringEdgeInfo { + index: self.edges.len(), + conditions_cost: condition_cost, + }) + } else { + None + }, + own_path_ids: self.own_path_ids.clone(), + overriding_path_ids: self.overriding_path_ids.clone(), + runtime_types_of_tail: Arc::new( + self.graph + .advance_possible_runtime_types(&self.runtime_types_of_tail, Some(new_edge))?, + ), + runtime_types_before_tail_if_last_is_cast: if matches!( + edge_weight.transition, + QueryGraphEdgeTransition::Downcast { .. } + ) { + Some(self.runtime_types_of_tail.clone()) + } else { + None + }, + // If there is no new `@defer` taking precedence, and the edge is downcast, then we + // inherit the prior version. This is because we only try to re-enter subgraphs for + // `@defer` on concrete fields, so as long as we add downcasts, we should remember that + // we still need to try re-entering the subgraph. + defer_on_tail: if defer.is_some() { + defer + } else if matches!( + edge_weight.transition, + QueryGraphEdgeTransition::Downcast { .. } + ) { + self.defer_on_tail.clone() + } else { + None + }, + }) + } + + pub(crate) fn iter(&self) -> impl Iterator> { + debug_assert_eq!(self.edges.len(), self.edge_triggers.len()); + debug_assert_eq!(self.edges.len(), self.edge_conditions.len()); + self.edges + .iter() + .copied() + .zip(&self.edge_triggers) + .zip(&self.edge_conditions) + .map(|((edge, trigger), condition)| (edge, trigger, condition)) + } + + pub(crate) fn next_edges<'a>( + &'a self, + ) -> Result + 'a>, FederationError> { + if self.defer_on_tail.is_some() { + // If the path enters a `@defer` (meaning that what comes after needs to be deferred), + // then it's the one special case where we explicitly need to ask for edges to self, + // as we will force the use of a `@key` edge (so we can send the non-deferred part + // immediately) and we may have to resume the deferred part in the same subgraph than + // the one in which we were (hence the need for edges to self). + return Ok(Box::new( + self.graph + .out_edges_with_federation_self_edges(self.tail) + .into_iter() + .map(|edge_ref| edge_ref.id()), + )); + } + + // In theory, we could always return `self.graph.out_edges(self.tail)` here. But in + // practice, `non_trivial_followup_edges` may give us a subset of those "out edges" that + // avoids some of the edges that we know we don't need to check because they are guaranteed + // to be inefficient after the last edge. Note that is purely an optimization (see + // https://github.com/apollographql/federation/pull/1653 for more details). + if let Some(last_edge) = self.edges.last() { + if let Some(last_edge) = (*last_edge).into() { + let Some(non_trivial_followup_edges) = + self.graph.non_trivial_followup_edges.get(&last_edge) + else { + return Err(FederationError::internal( + "Unexpectedly missing entry for non-trivial followup edges map", + )); + }; + return Ok(Box::new(non_trivial_followup_edges.iter().copied())); + } + } + + Ok(Box::new( + self.graph + .out_edges(self.tail) + .into_iter() + .map(|edge_ref| edge_ref.id()), + )) + } + + fn is_on_top_level_query_root(&self) -> Result { + let head_weight = self.graph.node_weight(self.head)?; + if !matches!(head_weight.type_, QueryGraphNodeType::FederatedRootType(_)) { + return Ok(false); + } + + // We walk the path's edges and as soon as we take a field (or the node is not a federated + // root or a subgraph root), we know we're not on the top-level query/mutation/subscription + // root anymore. The reason we don't just check that size <= 1 is that we could have a + // top-level `... on Query` inline fragment that doesn't actually change the type. + for edge in &self.edges { + let Some(edge) = (*edge).into() else { + continue; + }; + + let edge_weight = self.graph.edge_weight(edge)?; + if matches!( + edge_weight.transition, + QueryGraphEdgeTransition::FieldCollection { .. } + ) { + return Ok(false); + } + + let (_, tail) = self.graph.edge_endpoints(edge)?; + let tail_weight = self.graph.node_weight(tail)?; + let QueryGraphNodeType::SchemaType(tail_type_pos) = &tail_weight.type_ else { + return Err(FederationError::internal( + "Edge tail is unexpectedly a federated root", + )); + }; + + let tail_schema = self.graph.schema_by_source(&tail_weight.source)?; + let tail_schema_definition = &tail_schema.schema().schema_definition; + if let Some(query_type_name) = &tail_schema_definition.query { + if tail_type_pos.type_name() == &query_type_name.name { + continue; + } + } + if let Some(mutation_type_name) = &tail_schema_definition.mutation { + if tail_type_pos.type_name() == &mutation_type_name.name { + continue; + } + } + if let Some(subscription_type_name) = &tail_schema_definition.subscription { + if tail_type_pos.type_name() == &subscription_type_name.name { + continue; + } + } + return Ok(false); + } + Ok(true) + } + + fn tail_is_interface_object(&self) -> Result { + let tail_weight = self.graph.node_weight(self.tail)?; + + let QueryGraphNodeType::SchemaType(OutputTypeDefinitionPosition::Object( + tail_type_position, + )) = &tail_weight.type_ + else { + return Ok(false); + }; + + let subgraph_schema = self.graph.schema_by_source(&tail_weight.source)?; + let federation_spec_definition = + get_federation_spec_definition_from_subgraph(subgraph_schema)?; + let Some(interface_object_directive_definition) = + federation_spec_definition.interface_object_directive_definition(subgraph_schema)? + else { + return Ok(false); + }; + let tail_type = tail_type_position.get(subgraph_schema.schema())?; + Ok(tail_type + .directives + .iter() + .any(|directive| directive.name == interface_object_directive_definition.name)) + } + + fn last_edge_is_interface_object_fake_down_cast(&self) -> Result { + let Some(last_edge) = self.edges.last() else { + return Ok(false); + }; + let Some(last_edge) = (*last_edge).into() else { + return Ok(false); + }; + let last_edge_weight = self.graph.edge_weight(last_edge)?; + Ok(matches!( + last_edge_weight.transition, + QueryGraphEdgeTransition::InterfaceObjectFakeDownCast { .. } + )) + } + + // PORT_NOTE: In the JS codebase, this was named + // `lastIsIntefaceObjectFakeDownCastAfterEnteringSubgraph`. + fn last_edge_is_interface_object_fake_down_cast_after_entering_subgraph( + &self, + ) -> Result { + Ok(self.last_edge_is_interface_object_fake_down_cast()? + && self + .last_subgraph_entering_edge_info + .as_ref() + .map(|edge_info| edge_info.index) + // `len - 1` is the last index (the fake down cast), so `len - 2` is the previous edge. + == Some(self.edges.len() - 2)) + } + + fn can_satisfy_conditions( + &self, + edge: EdgeIndex, + condition_resolver: &mut impl ConditionResolver, + context: &OpGraphPathContext, + excluded_destinations: &ExcludedDestinations, + excluded_conditions: &ExcludedConditions, + ) -> Result { + let edge_weight = self.graph.edge_weight(edge)?; + if edge_weight.conditions.is_none() { + return Ok(ConditionResolution::no_conditions()); + } + let resolution = condition_resolver.resolve( + edge, + context, + excluded_destinations, + excluded_conditions, + )?; + if let Some(Some(last_edge)) = self.edges.last().map(|e| (*e).into()) { + if matches!( + edge_weight.transition, + QueryGraphEdgeTransition::FieldCollection { .. } + ) { + let last_edge_weight = self.graph.edge_weight(last_edge)?; + if !matches!( + last_edge_weight.transition, + QueryGraphEdgeTransition::KeyResolution + ) { + let in_same_subgraph = if let ConditionResolution::Satisfied { + path_tree: Some(path_tree), + .. + } = &resolution + { + path_tree.is_all_in_same_subgraph()? + } else { + true + }; + if in_same_subgraph { + let (edge_head, _) = self.graph.edge_endpoints(edge)?; + if self.graph.get_locally_satisfiable_key(edge_head)?.is_none() { + return Ok(ConditionResolution::Unsatisfied { + reason: Some(UnsatisfiedConditionReason::NoPostRequireKey), + }); + }; + // We're in a case where we have an `@requires` application (we have + // conditions and the new edge has a `FieldCollection` transition) and we + // have to jump to another subgraph to satisfy the `@requires`, which means + // we need to use a key on "the current subgraph" to resume collecting the + // field with the `@requires`. `get_locally_satisfiable_key()` essentially + // tells us that we have such key, and that's good enough here. Note that + // the way the code is organised, we don't use an actual edge of the query + // graph, so we cannot use `condition_resolver` and so it's not easy to get + // a proper cost or tree. That's ok in the sense that the cost of the key is + // negligible because we know it's a "local" one (there is no subgraph jump) + // and the code to build plan will deal with adding that key anyway (so not + // having the tree is ok). + // TODO(Sylvain): the whole handling of `@requires` is a bit too complex and + // hopefully we might be able to clean that up, but it's unclear to me how + // at the moment and it may not be a small change so this will have to do + // for now. + } + } + } + } + Ok(resolution) + } + + // TODO: We've skipped populating `Unadvanceables` information because it's only needed during + // composition, but we'll need to port that code when we port composition. + // PORT_NOTE: In the JS codebase, this was named + // `advancePathWithNonCollectingAndTypePreservingTransitions`. + fn advance_with_non_collecting_and_type_preserving_transitions( + self: &Arc, + context: &OpGraphPathContext, + condition_resolver: &mut impl ConditionResolver, + excluded_destinations: &ExcludedDestinations, + excluded_conditions: &ExcludedConditions, + transition_and_context_to_trigger: impl Fn( + &QueryGraphEdgeTransition, + &OpGraphPathContext, + ) -> TTrigger, + node_and_trigger_to_edge: impl Fn(&Arc, NodeIndex, &Arc) -> Option, + ) -> Result, FederationError> { + // If we're asked for indirect paths after an "@interfaceObject fake down cast" but that + // down cast comes just after non-collecting edge(s), then we can ignore the ask (skip + // indirect paths from there). The reason is that the presence of the non-collecting edges + // just before the fake down cast means we looked at indirect paths just before that + // down cast, but that fake down cast really does nothing in practice with the subgraph it's + // on, so any indirect path from that fake down cast will have a valid indirect path + // before it, and so will have been taken into account independently. + if self.last_edge_is_interface_object_fake_down_cast_after_entering_subgraph()? { + return Ok(IndirectPaths { + paths: Arc::new(vec![]), + dead_ends: Arc::new(Unadvanceables(vec![])), + }); + } + + let is_top_level_path = self.is_on_top_level_query_root()?; + let tail_weight = self.graph.node_weight(self.tail)?; + let tail_type_pos = if let QueryGraphNodeType::SchemaType(type_) = &tail_weight.type_ { + Some(type_) + } else { + None + }; + let original_source = tail_weight.source.clone(); + // For each source, we store the best path we find for that source with the score, or `None` + // if we can decide that we should not try going to that source (typically because we can + // prove that this create an inefficient detour for which a more direct path exists and will + // be found). + type BestPathInfo = + Option<(Arc>, QueryPlanCost)>; + let mut best_path_by_source: IndexMap> = + IndexMap::new(); + let dead_ends = vec![]; + // Note that through `excluded` we avoid taking the same edge from multiple options. But + // that means it's important we try the smallest paths first. That is, if we could in theory + // have path A -> B and A -> C -> B, and we can do B -> D, then we want to keep A -> B -> D, + // not A -> C -> B -> D. + let mut heap: BinaryHeap> = BinaryHeap::new(); + heap.push(HeapElement(self.clone())); + while let Some(HeapElement(to_advance)) = heap.pop() { + for edge in to_advance.next_edges()? { + let edge_weight = self.graph.edge_weight(edge)?; + if edge_weight.transition.collect_operation_elements() { + continue; + } + let (edge_head, edge_tail) = self.graph.edge_endpoints(edge)?; + let edge_tail_weight = self.graph.node_weight(edge_tail)?; + + if excluded_destinations.is_excluded(&edge_tail_weight.source) { + continue; + } + + // If the edge takes us back to the subgraph in which we started, we're not really + // interested (we've already checked for a direct transition from that original + // subgraph). One exception though is if we're just after a @defer, in which case + // re-entering the current subgraph is actually useful. + if edge_tail_weight.source == original_source && to_advance.defer_on_tail.is_none() + { + continue; + } + + // We have edges between Query objects so that if a field returns a query object, we + // can jump to any subgraph at that point. However, there is no point of using those + // edges at the beginning of a path, except for when we have a @defer, in which case + // we want to allow re-entering the same subgraph. + if is_top_level_path + && matches!( + edge_weight.transition, + QueryGraphEdgeTransition::RootTypeResolution { .. } + ) + && !(to_advance.defer_on_tail.is_some() + && self.graph.is_self_key_or_root_edge(edge)?) + { + continue; + } + + let prev_for_source = best_path_by_source.get(&edge_tail_weight.source); + let prev_for_source = match prev_for_source { + Some(Some(prev_for_source)) => Some(prev_for_source), + Some(None) => continue, + None => None, + }; + + if let Some(prev_for_source) = prev_for_source { + if (prev_for_source.0.edges.len() < to_advance.edges.len() + 1) + || (prev_for_source.0.edges.len() == to_advance.edges.len() + 1 + && prev_for_source.1 <= 1) + { + // We've already found another path that gets us to the same subgraph rather + // than the edge we're about to check. If that previous path is strictly + // shorter than the path we'd obtain with the new edge, then we don't + // consider this edge (it's a longer way to get to the same place). And if + // the previous path is the same size (as the one obtained with that edge), + // but that previous path's cost for getting the condition was 0 or 1, then + // the new edge cannot really improve on this and we don't bother with it. + // + // Note that a cost of 0 can only happen during composition validation where + // all costs are 0 to mean "we don't care about costs". This effectively + // means that for validation, as soon as we have a path to a subgraph, we + // ignore other options even if they may be "faster". + continue; + } + } + + if excluded_conditions.is_excluded(edge_weight.conditions.as_ref()) { + continue; + } + + // As we validate the condition for this edge, it might be necessary to jump to + // another subgraph, but if for that we need to jump to the same subgraph we're + // trying to get to, then it means there is another, shorter way to go to our + // destination and we can return that shorter path, not the one with the edge + // we're trying. + let condition_resolution = to_advance.can_satisfy_conditions( + edge, + condition_resolver, + context, + &excluded_destinations.add_excluded(edge_tail_weight.source.clone()), + excluded_conditions, + )?; + if let ConditionResolution::Satisfied { path_tree, cost } = condition_resolution { + // We can get to `edge_tail_weight.source` with that edge. But if we had already + // found another path to the same subgraph, we want to replace it with this one + // only if either 1) it is shorter or 2) if it's of equal size, only if the + // condition cost is lower than the previous one. + if let Some(prev_for_source) = prev_for_source { + if prev_for_source.0.edges.len() == to_advance.edges.len() + 1 + && prev_for_source.1 <= cost + { + continue; + } + } + + // It's important we minimize the number of options this method returns, because + // during query planning with many fields, options here translate to state + // explosion. This is why above we eliminated edges that provably have better + // options. + // + // But we can do a slightly more involved check. Suppose we have a few subgraphs + // A, B and C, and suppose that we're considering an edge from B to C. We can + // then look at which subgraph we were in before reaching B (which can be "none" + // if the query starts at B), and let say that it is A. In other words, if we + // use the edge we're considering, we'll be looking at a path like: + // ... -> A -> B -> -> C + // + // Now, we can fairly easily check if the fields we collected in B (the ``) can be also collected directly (without keys, nor requires) + // from A and if after that we could take an edge to C. If we can do all that, + // then we know that the path we're considering is strictly less efficient than: + // ... -> A -> -> C + // + // Furthermore, since we've confirmed its a valid path, it will be found by + // another branch of the algorithm. In that case, we can ignore the edge to C, + // knowing a better path exists. Doing this drastically reduces state explosion + // in a number of cases. + if let Some(last_subgraph_entering_edge_info) = + &to_advance.last_subgraph_entering_edge_info + { + let Some(last_subgraph_entering_edge) = + to_advance.edges[last_subgraph_entering_edge_info.index].into() + else { + return Err(FederationError::internal( + "Subgraph-entering edge is unexpectedly absent", + )); + }; + + let (last_subgraph_entering_edge_head, last_subgraph_entering_edge_tail) = + self.graph.edge_endpoints(last_subgraph_entering_edge)?; + let last_subgraph_entering_edge_tail_weight = + self.graph.node_weight(last_subgraph_entering_edge_tail)?; + let QueryGraphNodeType::SchemaType( + last_subgraph_entering_edge_tail_type_pos, + ) = &last_subgraph_entering_edge_tail_weight.type_ + else { + return Err(FederationError::internal( + "Subgraph-entering edge tail is unexpectedly a federated root", + )); + }; + if Some(last_subgraph_entering_edge_tail_type_pos) != tail_type_pos { + let last_subgraph_entering_edge_weight = + self.graph.edge_weight(last_subgraph_entering_edge)?; + + // If the previous subgraph is an actual subgraph, the head of the last + // subgraph-entering edge would be where a direct path starts. If the + // previous subgraph is a federated root, we instead take the previous + // subgraph to be the destination subgraph of this edge, and that + // subgraph's root of the same root kind (if it exists) would be where a + // direct path starts. + let direct_path_start_node = if matches!( + last_subgraph_entering_edge_weight.transition, + QueryGraphEdgeTransition::SubgraphEnteringTransition + ) { + let root = to_advance.head; + let root_weight = self.graph.node_weight(root)?; + let QueryGraphNodeType::FederatedRootType(root_kind) = + &root_weight.type_ + else { + return Err(FederationError::internal("Encountered non-root path with a subgraph-entering transition")); + }; + self.graph + .root_kinds_to_nodes_by_source(&edge_tail_weight.source)? + .get(root_kind) + .copied() + } else { + Some(last_subgraph_entering_edge_head) + }; + + // If the previous subgraph is a federated root, as noted above we take + // the previous subgraph to instead be the destination subgraph of this + // edge, so we must manually indicate that here. + let is_edge_to_previous_subgraph = if matches!( + last_subgraph_entering_edge_weight.transition, + QueryGraphEdgeTransition::SubgraphEnteringTransition + ) { + true + } else { + let last_subgraph_entering_edge_head_weight = + self.graph.node_weight(last_subgraph_entering_edge_head)?; + last_subgraph_entering_edge_head_weight.source + == last_subgraph_entering_edge_tail_weight.source + }; + + let direct_path_end_node = + if let Some(direct_path_start_node) = direct_path_start_node { + let QueryGraphNodeType::SchemaType(edge_tail_type_pos) = + &edge_tail_weight.type_ + else { + return Err(FederationError::internal( + "Edge tail is unexpectedly a federated root", + )); + }; + self.check_direct_path_from_node( + last_subgraph_entering_edge_info.index + 1, + direct_path_start_node, + edge_tail_type_pos, + &node_and_trigger_to_edge, + )? + } else { + None + }; + + if let Some(direct_path_end_node) = direct_path_end_node { + let direct_key_edge_max_cost = last_subgraph_entering_edge_info + .conditions_cost + + if is_edge_to_previous_subgraph { + 0 + } else { + cost + }; + if is_edge_to_previous_subgraph + || self.graph.has_satisfiable_direct_key_edge( + direct_path_end_node, + &edge_tail_weight.source, + condition_resolver, + direct_key_edge_max_cost, + )? + { + // We just found that going to the previous subgraph is useless + // because there is a more direct path. But we additionally + // record that this previous subgraph should be avoided + // altogether because some other longer path could try to get + // back to that same source but defeat this specific check due + // to having taken another edge first (and thus the last + // subgraph-entering edge is different). + // + // What we mean here is that if `to_advance` path is + // ... -> A -> B -> + // and we just found that we don't want to keep + // ... -> A -> B -> -> A + // because we know + // ... -> A -> + // is possible directly, then we don't want this + // method to later add + // ... -> A -> B -> -> C -> A + // as that is equally not useful. + best_path_by_source + .insert(edge_tail_weight.source.clone(), None); + continue; + } + } + } + } + + let updated_path = Arc::new(to_advance.add( + transition_and_context_to_trigger(&edge_weight.transition, context), + edge.into(), + ConditionResolution::Satisfied { cost, path_tree }, + None, + )?); + best_path_by_source.insert( + edge_tail_weight.source.clone(), + Some((updated_path.clone(), cost)), + ); + // It can be necessary to "chain" keys, because different subgraphs may have + // different keys exposed, and so we when we took a key, we want to check if + // there is a new key we can now use that takes us to other subgraphs. For other + // non-collecting edges ('RootTypeResolution' and 'SubgraphEnteringTransition') + // however, chaining never give us additional value. + // + // One exception is the case of self-edges (which stay on the same node), as + // those will only be looked at just after a @defer to handle potentially + // re-entering the same subgraph. When we take this, there's no point in looking + // for chaining since we'll independently check the other edges already. + if matches!( + edge_weight.transition, + QueryGraphEdgeTransition::KeyResolution + ) { + let edge_head_weight = self.graph.node_weight(edge_head)?; + if edge_head_weight.source != edge_tail_weight.source { + heap.push(HeapElement(updated_path)); + } + } + } + } + } + + Ok(IndirectPaths { + paths: Arc::new( + best_path_by_source + .into_values() + .flatten() + .map(|p| p.0) + .collect(), + ), + dead_ends: Arc::new(Unadvanceables(dead_ends)), + }) + } + + /// Checks whether the partial path starting at the given edge index has an alternative path + /// starting from the given node, where only direct edges are considered, and returns the node + /// such a path ends on if it exists. Additionally, this method checks that the ending node has + /// the given type. + // PORT_NOTE: In the JS codebase, this was named `checkDirectPathFromPreviousSubgraphTo`. We've + // also generalized this method a bit by shifting certain logic into the caller. + fn check_direct_path_from_node( + &self, + start_index: usize, + start_node: NodeIndex, + end_type_position: &OutputTypeDefinitionPosition, + node_and_trigger_to_edge: impl Fn(&Arc, NodeIndex, &Arc) -> Option, + ) -> Result, FederationError> { + let mut current_node = start_node; + for index in start_index..self.edges.len() { + let trigger = &self.edge_triggers[index]; + let Some(edge) = node_and_trigger_to_edge(&self.graph, current_node, trigger) else { + return Ok(None); + }; + + // If the edge is `None`, this means the trigger doesn't require taking an edge (it's + // typically an inline fragment with no type condition, just directives), which we can + // always match. + let Some(edge) = edge.into() else { + continue; + }; + + // If the edge has conditions, we don't consider it a direct path as we don't know if + // that condition can be satisfied and at what cost. + let edge_weight = self.graph.edge_weight(edge)?; + if edge_weight.conditions.is_some() { + return Ok(None); + } + + current_node = self.graph.edge_endpoints(edge)?.1; + } + + // If we got here, that means we were able to match all the triggers on the partial path, + // and so assuming we're on the proper type, we have a direct path from the start node. + let current_node_weight = self.graph.node_weight(current_node)?; + let QueryGraphNodeType::SchemaType(type_pos) = ¤t_node_weight.type_ else { + return Ok(None); + }; + Ok(if type_pos == end_type_position { + Some(current_node) + } else { + None + }) + } +} + +/// `BinaryHeap::pop` returns the "greatest" element. We want the one with the fewest edges. +/// This wrapper compares by *reverse* comparison of edge count. +struct HeapElement(Arc>) +where + TTrigger: Eq + Hash, + Arc: Into, + TEdge: Copy + Into>, + EdgeIndex: Into; + +impl PartialEq for HeapElement +where + TTrigger: Eq + Hash, + Arc: Into, + TEdge: Copy + Into>, + EdgeIndex: Into, +{ + fn eq(&self, other: &HeapElement) -> bool { + self.0.edges.len() == other.0.edges.len() + } +} + +impl Eq for HeapElement +where + TTrigger: Eq + Hash, + Arc: Into, + TEdge: Copy + Into>, + EdgeIndex: Into, +{ +} + +impl PartialOrd for HeapElement +where + TTrigger: Eq + Hash, + Arc: Into, + TEdge: Copy + Into>, + EdgeIndex: Into, +{ + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for HeapElement +where + TTrigger: Eq + Hash, + Arc: Into, + TEdge: Copy + Into>, + EdgeIndex: Into, +{ + fn cmp(&self, other: &Self) -> Ordering { + self.0.edges.len().cmp(&other.0.edges.len()).reverse() + } +} + +impl OpGraphPath { + fn next_edge_for_field(&self, field: &Field) -> Option { + self.graph.edge_for_field(self.tail, field) + } + + fn next_edge_for_inline_fragment(&self, inline_fragment: &InlineFragment) -> Option { + self.graph + .edge_for_inline_fragment(self.tail, inline_fragment) + } + + fn add_field_edge( + &self, + operation_field: Field, + edge: EdgeIndex, + condition_resolver: &mut impl ConditionResolver, + context: &OpGraphPathContext, + ) -> Result, FederationError> { + let condition_resolution = self.can_satisfy_conditions( + edge, + condition_resolver, + context, + &Default::default(), + &Default::default(), + )?; + if matches!(condition_resolution, ConditionResolution::Satisfied { .. }) { + self.add( + operation_field.into(), + edge.into(), + condition_resolution, + None, + ) + .map(Some) + } else { + Ok(None) + } + } + + pub(crate) fn mark_overriding( + &self, + others: &[SimultaneousPaths], + ) -> (OpGraphPath, Vec) { + let new_id = OverrideId::new(); + let mut new_own_path_ids = self.overriding_path_ids.as_ref().clone(); + new_own_path_ids.insert(new_id); + let new_self = OpGraphPath { + own_path_ids: Arc::new(new_own_path_ids), + ..self.clone() + }; + let new_others = others + .iter() + .map(|option| { + SimultaneousPaths( + option + .0 + .iter() + .map(|path| { + let mut new_overriding_path_ids = + path.overriding_path_ids.as_ref().clone(); + new_overriding_path_ids.insert(new_id); + Arc::new(OpGraphPath { + overriding_path_ids: Arc::new(new_overriding_path_ids), + ..path.as_ref().clone() + }) + }) + .collect(), + ) + }) + .collect(); + (new_self, new_others) + } + + pub(crate) fn is_overridden_by(&self, other: &Self) -> bool { + self.overriding_path_ids + .iter() + .any(|overriding_id| other.own_path_ids.contains(overriding_id)) + } + + pub(crate) fn subgraph_jumps(&self) -> Result { + self.subgraph_jumps_at_idx(0) + } + + fn subgraph_jumps_at_idx(&self, start_index: usize) -> Result { + self.edges[start_index..] + .iter() + .flatten() + .try_fold(0, |sum, &edge_index| { + let (start, end) = self.graph.edge_endpoints(edge_index)?; + let start = self.graph.node_weight(start)?; + let end = self.graph.node_weight(end)?; + let changes_subgraph = start.source != end.source; + Ok(sum + if changes_subgraph { 1 } else { 0 }) + }) + } + + fn find_longest_common_prefix_length( + &self, + other: &OpGraphPath, + ) -> Result { + if self.head != other.head { + return Err(FederationError::internal( + "Paths unexpectedly did not start at the same node.", + )); + } + + Ok(self + .edges + .iter() + .zip(&other.edges) + .position(|(self_edge, other_edge)| self_edge != other_edge) + .unwrap_or_else(|| self.edges.len().min(other.edges.len()))) + } + + /// Looks for the longest common prefix for `self` and `other` (assuming that both paths are + /// built as options for the same "query path"), and then compares whether each path has + /// subgraph jumps after said prefix. + /// + /// Note this method always return something, but the longest common prefix considered may very + /// well be empty. Also note that this method assumes that the 2 paths have the same root, and + /// will fail if that's not the case. + /// + /// Returns the comparison of whether `self` and `other` have subgraph jumps after said prefix + /// (e.g. `Ordering::Less` means `self` has zero subgraph jumps after said prefix while `other` + /// has at least one). If they both have subgraph jumps or neither has subgraph jumps, then we + /// return `Ordering::Equal`. + fn compare_subgraph_jumps_after_last_common_node( + &self, + other: &OpGraphPath, + ) -> Result { + let longest_common_prefix_len = self.find_longest_common_prefix_length(other)?; + let self_jumps = self.subgraph_jumps_at_idx(longest_common_prefix_len)? > 0; + let other_jumps = other.subgraph_jumps_at_idx(longest_common_prefix_len)? > 0; + Ok(self_jumps.cmp(&other_jumps)) + } + + pub(crate) fn terminate_with_non_requested_typename_field( + &self, + ) -> Result { + // If the last step of the path was a fragment/type-condition, we want to remove it before + // we get __typename. The reason is that this avoid cases where this method would make us + // build plans like: + // { + // foo { + // __typename + // ... on A { + // __typename + // } + // ... on B { + // __typename + // } + // } + // Instead, we just generate: + // { + // foo { + // __typename + // } + // } + // Note it's ok to do this because the __typename we add is _not_ requested, it is just + // added in cases where we need to ensure a selection is not empty, and so this + // transformation is fine to do. + let path = self.truncate_trailing_downcasts()?; + let tail_weight = self.graph.node_weight(path.tail)?; + let QueryGraphNodeType::SchemaType(tail_type_pos) = &tail_weight.type_ else { + return Err(FederationError::internal( + "Unexpectedly found federated root node as tail", + )); + }; + let Ok(tail_type_pos) = CompositeTypeDefinitionPosition::try_from(tail_type_pos.clone()) + else { + return Ok(path); + }; + let typename_field = Field::new_introspection_typename( + self.graph.schema_by_source(&tail_weight.source)?, + &tail_type_pos, + None, + ); + let Some(edge) = self.graph.edge_for_field(path.tail, &typename_field) else { + return Err(FederationError::internal( + "Unexpectedly missing edge for __typename field", + )); + }; + path.add( + typename_field.into(), + Some(edge), + ConditionResolution::no_conditions(), + None, + ) + } + + /// Remove all trailing downcast edges and `None` edges. + fn truncate_trailing_downcasts(&self) -> Result { + let mut runtime_types = Arc::new(self.head_possible_runtime_types()?); + let mut last_edge_index = None; + let mut last_runtime_types = runtime_types.clone(); + for (edge_index, edge) in self.edges.iter().enumerate() { + runtime_types = Arc::new( + self.graph + .advance_possible_runtime_types(&runtime_types, *edge)?, + ); + let Some(edge) = edge else { + continue; + }; + let edge_weight = self.graph.edge_weight(*edge)?; + if !matches!( + edge_weight.transition, + QueryGraphEdgeTransition::Downcast { .. } + ) { + last_edge_index = Some(edge_index); + last_runtime_types = runtime_types.clone(); + } + } + let Some(last_edge_index) = last_edge_index else { + // PORT_NOTE: The JS codebase just returns the same path if all edges are downcast or + // `None` edges. This is likely a bug, so we instead return the empty path here. + return OpGraphPath::new(self.graph.clone(), self.head); + }; + let prefix_length = last_edge_index + 1; + if prefix_length == self.edges.len() { + return Ok(self.clone()); + } + let Some(last_edge) = self.edges[last_edge_index] else { + return Err(FederationError::internal( + "Unexpectedly found None for last non-downcast, non-None edge", + )); + }; + let (_, last_edge_tail) = self.graph.edge_endpoints(last_edge)?; + Ok(OpGraphPath { + graph: self.graph.clone(), + head: self.head, + tail: last_edge_tail, + edges: self.edges[0..prefix_length].to_vec(), + edge_triggers: self.edge_triggers[0..prefix_length].to_vec(), + edge_conditions: self.edge_conditions[0..prefix_length].to_vec(), + last_subgraph_entering_edge_info: self.last_subgraph_entering_edge_info.clone(), + own_path_ids: self.own_path_ids.clone(), + overriding_path_ids: self.overriding_path_ids.clone(), + runtime_types_of_tail: last_runtime_types, + runtime_types_before_tail_if_last_is_cast: None, + // TODO: The JS codebase copied this from the current path, which seems like a bug. + defer_on_tail: self.defer_on_tail.clone(), + }) + } + + pub(crate) fn is_equivalent_save_for_type_explosion_to( + &self, + other: &OpGraphPath, + ) -> Result { + // We're looking at the specific case where both paths are basically equivalent except for a + // single step of type-explosion, so if either of the paths don't start and end on the + // same node, or if `other` is not exactly 1 more step than `self`, we're done. + if !(self.head == other.head + && self.tail == other.tail + && self.edges.len() == other.edges.len() - 1) + { + return Ok(false); + } + + // If the above is true, then we find the first difference in the paths. + let Some(diff_pos) = self + .edges + .iter() + .zip(&other.edges) + .position(|(self_edge, other_edge)| self_edge != other_edge) + else { + // All edges are the same, but the `other` path has an extra edge. This can't be a type + // explosion + key resolution, so we consider them not equivalent here. + // + // PORT_NOTE: The JS codebase returns `true` here, claiming the paths are the same. This + // isn't true though as we're skipping the last element of `other` in the JS codebase + // (and while that edge can't change the `tail`, it doesn't mean that `self` subsumes + // `other`). We fix this bug here by returning `false` instead of `true`. + return Ok(false); + }; + + // If the first difference is not a "type-explosion", i.e. if `other` is a cast from an + // interface to one of the implementation, then we're not in the case we're looking for. + let Some(self_edge) = self.edges[diff_pos] else { + return Ok(false); + }; + let Some(other_edge) = self.edges[diff_pos] else { + return Ok(false); + }; + let other_edge_weight = other.graph.edge_weight(other_edge)?; + let QueryGraphEdgeTransition::Downcast { + from_type_position, .. + } = &other_edge_weight.transition + else { + return Ok(false); + }; + if !matches!( + from_type_position, + CompositeTypeDefinitionPosition::Interface(_) + ) { + return Ok(false); + } + + // At this point, we want both paths to take the "same" key, but because one is starting + // from the interface while the other one from an implementation, they won't be technically + // the "same" edge index. So we check that both are key-resolution edges, to the same + // subgraph and type, and with the same condition. + let Some(other_next_edge) = self.edges[diff_pos + 1] else { + return Ok(false); + }; + let (_, self_edge_tail) = other.graph.edge_endpoints(self_edge)?; + let self_edge_weight = other.graph.edge_weight(self_edge)?; + let (_, other_next_edge_tail) = other.graph.edge_endpoints(other_next_edge)?; + let other_next_edge_weight = other.graph.edge_weight(other_next_edge)?; + if !(matches!( + self_edge_weight.transition, + QueryGraphEdgeTransition::KeyResolution + ) && matches!( + other_next_edge_weight.transition, + QueryGraphEdgeTransition::KeyResolution + ) && self_edge_tail == other_next_edge_tail + && self_edge_weight.conditions == other_next_edge_weight.conditions) + { + return Ok(false); + } + + // So far, so good. Check that the rest of the paths are equal. Note that starts with + // `diff_pos + 1` for `self`, but `diff_pos + 2` for `other` since we looked at two edges + // there instead of one. + return Ok(self.edges[(diff_pos + 1)..] + .iter() + .zip(other.edges[(diff_pos + 2)..].iter()) + .all(|(self_edge, other_edge)| self_edge == other_edge)); + } + + /// This method is used to detect when using an interface field "directly" could fail (i.e. lead + /// to a dead end later for the query path) while type-exploding may succeed. + /// + /// In general, taking a field from an interface directly or through it's implementation by + /// type-exploding leads to the same option, and so taking one or the other is more of a matter + /// of "which is more efficient". But there is a special case where this may not be true, and + /// this is when all of the following hold: + /// 1. The interface is implemented by an entity type. + /// 2. The field being looked at is @shareable. + /// 3. The field type has a different set of fields (and less fields) in the "current" subgraph + /// than in another one. + /// + /// For instance, consider if some Subgraph A has this schema: + /// """ + /// type Query { + /// i: I + /// } + /// + /// interface I { + /// s: S + /// } + /// + /// type T implements I @key(fields: "id") { + /// id: ID! + /// s: S @shareable + /// } + /// + /// type S @shareable { + /// x: Int + /// } + /// """ + /// and if some Subgraph B has this schema: + /// """ + /// type T @key(fields: "id") { + /// id: ID! + /// s: S @shareable + /// } + /// + /// type S @shareable { + /// x: Int + /// y: Int + /// } + /// """ + /// and suppose that `{ i { s { y } } }` is queried. If we follow `I.s` in subgraph A then the + /// `y` field cannot be found, because `S` not being an entity means we cannot "jump" to + /// subgraph B (even if it was, there may not be a usable key to jump between the 2 subgraphs). + /// However, if we "type-explode" into implementation `T`, then we can jump to subgraph B from + /// that, at which point we can reach `y`. + /// + /// So the goal of this method is to detect when we might be in such a case: when we are, we + /// will have to consider type explosion on top of the direct route in case that direct route + /// ends up "not panning out" (note that by the time this method is called, we're only looking + /// at the options for type `I.s`; we do not know yet if `y` is queried next and so cannot tell + /// if type explosion will be necessary or not). + // PORT_NOTE: In the JS code, this method was a free-standing function called "anImplementationIsEntityWithFieldShareable". + fn has_an_entity_implementation_with_shareable_field( + &self, + _source: &NodeStr, + itf: InterfaceFieldDefinitionPosition, + ) -> Result { + let valid_schema = self.graph.schema()?; + let schema = valid_schema.schema(); + let fed_spec = get_federation_spec_definition_from_subgraph(valid_schema)?; + let key_directive = fed_spec.key_directive_definition(valid_schema)?; + let shareable_directive = fed_spec.shareable_directive(valid_schema)?; + let comp_type_pos = CompositeTypeDefinitionPosition::Interface(itf.parent()); + for implem in valid_schema.possible_runtime_types(comp_type_pos)? { + let ty = implem.get(schema)?; + let field = ty.fields.get(&itf.field_name).ok_or_else(|| { + FederationError::internal( + "Unable to find interface field ({itf}) in schema: {schema}", + ) + })?; + if !ty.directives.has(&key_directive.name) { + continue; + } + if !field.directives.has(&shareable_directive.name) { + continue; + } + + // Returning `true` for this method has a cost: it will make us consider type-explosion for `itf`, and this can + // sometime lead to a large number of additional paths to explore, which can have a substantial cost. So we want + // to limit it if we can avoid it. As it happens, we should return `true` if it is possible that "something" + // (some field) in the type of `field` is reachable in _another_ subgraph but no in the one of the current path. + // And while it's not trivial to check this in general, there are some easy cases we can eliminate. For instance, + // if the type in the current subgraph has only leaf fields, we can check that all other subgraphs reachable + // from the implementation have the same set of leaf fields. + let base_ty_name = field.ty.inner_named_type(); + if is_leaf_type(schema, base_ty_name) { + continue; + } + let Some(ty) = schema.get_object(base_ty_name) else { + return Ok(true); + }; + if ty + .fields + .values() + .any(|f| !is_leaf_type(schema, f.ty.inner_named_type())) + { + return Ok(true); + } + for node in self.graph.nodes_for_type(&ty.name) { + let node = self.graph.node_weight(node)?; + let tail = self.graph.node_weight(self.tail)?; + if node.source == tail.source { + continue; + } + let Some(src) = self.graph.sources.get(&node.source) else { + return Err(FederationError::internal(format!( + "{node} has no valid schema in QueryGraph: {:?}", + self.graph + ))); + }; + let fed_spec = get_federation_spec_definition_from_subgraph(src)?; + let shareable_directive = fed_spec.shareable_directive(src)?; + let build_err = || { + Err(FederationError::internal(format!( + "{implem} is an object in {} but a {} in {}", + tail.source, node.type_, node.source + ))) + }; + let QueryGraphNodeType::SchemaType(node_ty) = &node.type_ else { + return build_err(); + }; + let node_ty = node_ty.get(schema)?; + let other_fields = match node_ty { + ExtendedType::Object(obj) => &obj.fields, + ExtendedType::Interface(int) => &int.fields, + _ => return build_err(), + }; + let Some(field) = other_fields.get(&itf.field_name) else { + continue; + }; + if !field.directives.has(&shareable_directive.name) { + continue; + } + let field_ty = field.ty.inner_named_type(); + if field_ty != base_ty_name + || !(schema.get_object(field_ty).is_some() + || schema.get_interface(field_ty).is_some()) + { + // We have a genuine difference here, so we should explore type explosion. + return Ok(true); + } + let names: HashSet<_> = other_fields.keys().collect(); + if !ty.fields.keys().all(|f| names.contains(&f)) { + // Same, we have a genuine difference. + return Ok(true); + } + } + return Ok(false); + } + Ok(false) + } + + /// For the first element of the pair, the data has the same meaning as in + /// `SimultaneousPathsWithLazyIndirectPaths.advance_with_operation_element()`. We also actually + /// need to return a `Vec` of options of simultaneous paths (because when we type explode, we + /// create simultaneous paths, but as a field might be resolved by multiple subgraphs, we may + /// have also created multiple options). + /// + /// For the second element, it is true if the result only has type-exploded results. + fn advance_with_operation_element( + &self, + supergraph_schema: ValidFederationSchema, + operation_element: &OpPathElement, + context: &OpGraphPathContext, + condition_resolver: &mut impl ConditionResolver, + ) -> Result<(Option>, Option), FederationError> { + let tail_weight = self.graph.node_weight(self.tail)?; + let QueryGraphNodeType::SchemaType(tail_type_pos) = &tail_weight.type_ else { + // We cannot advance any operation from here. We need to take the initial non-collecting + // edges first. + return Ok((None, None)); + }; + match operation_element { + OpPathElement::Field(operation_field) => { + match tail_type_pos { + OutputTypeDefinitionPosition::Object(tail_type_pos) => { + // Just take the edge corresponding to the field, if it exists and can be + // used. + let Some(edge) = self.next_edge_for_field(operation_field) else { + return Ok((None, None)); + }; + + // If the tail type is an `@interfaceObject`, it's possible that the + // requested field is a field of an implementation of the interface. Because + // we found an edge, we know that the interface object has the field and we + // can use the edge. However, we can't add the operation field as-is to this + // path, since it's referring to a parent type that is not in the current + // subgraph. We must instead use the tail's type, so we change the field + // accordingly. + // + // TODO: It would be good to understand what parts of query planning rely + // on triggers being valid within a subgraph. + let mut operation_field = operation_field.clone(); + if self.tail_is_interface_object()? + && *operation_field.data().field_position.type_name() + != tail_type_pos.type_name + { + let field_on_tail_type = tail_type_pos + .field(operation_field.data().field_position.field_name().clone()); + if field_on_tail_type + .try_get(self.graph.schema_by_source(&tail_weight.source)?.schema()) + .is_none() + { + let edge_weight = self.graph.edge_weight(edge)?; + return Err(FederationError::internal(format!( + "Unexpectedly missing {} for {} from path {}", + operation_field, edge_weight, self, + ))); + } + operation_field = Field::new(FieldData { + schema: self.graph.schema_by_source(&tail_weight.source)?.clone(), + field_position: field_on_tail_type.into(), + alias: operation_field.data().alias.clone(), + arguments: operation_field.data().arguments.clone(), + directives: operation_field.data().directives.clone(), + sibling_typename: operation_field.data().sibling_typename.clone(), + }) + } + + let field_path = self.add_field_edge( + operation_field, + edge, + condition_resolver, + context, + )?; + Ok((field_path.map(|p| vec![p.into()]), None)) + } + OutputTypeDefinitionPosition::Interface(tail_type_pos) => { + // Due to `@interfaceObject`, we could be in a case where the field asked is + // not on the interface but rather on one of it's implementations. This can + // happen if we just entered the subgraph on an interface `@key` and are + // and coming from an `@interfaceObject`. In that case, we'll skip checking + // for a direct interface edge and simply cast into that implementation + // below. + let field_is_of_an_implementation = + *operation_field.data().field_position.type_name() + != tail_type_pos.type_name; + + // First, we check if there is a direct edge from the interface (which only + // happens if we're in a subgraph that knows all of the implementations of + // that interface globally and all of them resolve the field). If there is + // one, then we have 2 options: + // - We take that edge. + // - We type-explode (like when we don't have a direct interface edge). + // We want to avoid looking at both options if we can because it multiplies + // planning work quickly if we always check both options. And in general, + // taking the interface edge is better than type explosion "if it works", + // so we distinguish a number of cases where we know that either: + // - Type-exploding cannot work unless taking the interface edge also does + // (the `has_an_entity_implementation_with_shareable_field()` call). + // - Type-exploding cannot be more efficient than the direct path (when no + // `@provides` are involved; if a `@provides` is involved in one of the + // implementations, then type-exploding may lead to a shorter overall + // plan thanks to that `@provides`). + let interface_edge = if field_is_of_an_implementation { + None + } else { + self.next_edge_for_field(operation_field) + }; + let interface_path = if let Some(interface_edge) = &interface_edge { + let field_path = self.add_field_edge( + operation_field.clone(), + *interface_edge, + condition_resolver, + context, + )?; + if field_path.is_none() { + let interface_edge_weight = + self.graph.edge_weight(*interface_edge)?; + return Err(FederationError::internal(format!( + "Interface edge {} unexpectedly had conditions", + interface_edge_weight + ))); + } + field_path + } else { + None + }; + let direct_path_overrides_type_explosion = + if let Some(interface_edge) = &interface_edge { + // There are 2 separate cases where we going to do both "direct" and + // "type-exploding" options: + // 1. There is an `@provides`: in that case the "type-exploding + // case can legitimately be more efficient and we want to = + // consider it "all the way" + // 2. In the sub-case of + // `!has_an_entity_implementation_with_shareable_field(...)`, + // where we want to have the type-exploding option only for the + // case where the "direct" one fails later. But in that case, + // we'll remember that if the direct option pans out, then we can + // ignore the type-exploding one. + // `direct_path_overrides_type_explosion` indicates that we're in + // the 2nd case above, not the 1st one. + operation_field + .data() + .field_position + .is_introspection_typename_field() + || (!self.graph.is_provides_edge(*interface_edge)? + && !self.graph.has_an_implementation_with_provides( + &tail_weight.source, + tail_type_pos.field( + operation_field + .data() + .field_position + .field_name() + .clone(), + ), + )?) + } else { + false + }; + if direct_path_overrides_type_explosion { + // We can special-case terminal (leaf) fields: as long they have no + // `@provides`, then the path ends there and there is no need to check + // type explosion "in case the direct path doesn't pan out". + // Additionally, if we're not in the case where an implementation + // is an entity with a shareable field, then there is no case where the + // direct case wouldn't "pan out" but the type explosion would, so we + // can ignore type-exploding there too. + // + // TODO: We should re-assess this when we support `@requires` on + // interface fields (typically, should we even try to type-explode + // if the direct edge cannot be satisfied? Probably depends on the exact + // semantics of `@requires` on interface fields). + let operation_field_type_name = operation_field + .data() + .field_position + .get(operation_field.data().schema.schema())? + .ty + .inner_named_type(); + let is_operation_field_type_leaf = matches!( + operation_field + .data() + .schema + .get_type(operation_field_type_name.clone())?, + TypeDefinitionPosition::Scalar(_) | TypeDefinitionPosition::Enum(_) + ); + if is_operation_field_type_leaf + && self.has_an_entity_implementation_with_shareable_field( + &tail_weight.source, + tail_type_pos.field( + operation_field.data().field_position.field_name().clone(), + ), + )? + { + let Some(interface_path) = interface_path else { + return Err(FederationError::internal( + "Unexpectedly missing interface path", + )); + }; + return Ok((Some(vec![interface_path.into()]), None)); + } + } + + // There are 2 main cases to handle here: + // - The most common is that it's a field of the interface that is queried, + // and so we should type-explode because either we didn't had a direct + // edge, or `@provides` makes it potentially worthwhile to check with type + // explosion. + // - But, as mentioned earlier, we could be in the case where the field + // queried is actually of one of the implementation of the interface. In + // that case, we only want to consider that one implementation. + let implementations = if field_is_of_an_implementation { + let CompositeTypeDefinitionPosition::Object(field_parent_pos) = + &operation_field.data().field_position.parent() + else { + return Err(FederationError::internal( + format!( + "{} requested on {}, but field's parent {} is not an object type", + operation_field.data().field_position, + tail_type_pos, + operation_field.data().field_position.type_name() + ) + )); + }; + if !self.runtime_types_of_tail.contains(field_parent_pos) { + return Err(FederationError::internal( + format!( + "{} requested on {}, but field's parent {} is not an implementation type", + operation_field.data().field_position, + tail_type_pos, + operation_field.data().field_position.type_name() + ) + )); + } + Arc::new(IndexSet::from([field_parent_pos.clone()])) + } else { + self.runtime_types_of_tail.clone() + }; + + // We type-explode. For all implementations, we need to call + // `advance_with_operation_element()` on a made-up inline fragment. If + // any gives us empty options, we bail. + let mut options_for_each_implementation = vec![]; + for implementation_type_pos in implementations.as_ref() { + let implementation_inline_fragment = + InlineFragment::new(InlineFragmentData { + schema: self + .graph + .schema_by_source(&tail_weight.source)? + .clone(), + parent_type_position: tail_type_pos.clone().into(), + type_condition_position: Some( + implementation_type_pos.clone().into(), + ), + directives: Default::default(), + selection_id: SelectionId::new(), + }); + let implementation_options = + SimultaneousPathsWithLazyIndirectPaths::new( + self.clone().into(), + context.clone(), + Default::default(), + Default::default(), + ) + .advance_with_operation_element( + supergraph_schema.clone(), + &implementation_inline_fragment.into(), + condition_resolver, + )?; + // If we find no options for that implementation, we bail (as we need to + // simultaneously advance all implementations). + let Some(mut implementation_options) = implementation_options else { + return Ok((interface_path.map(|p| vec![p.into()]), None)); + }; + // If the new inline fragment makes it so that we're on an unsatisfiable + // branch, we just ignore that implementation. + if implementation_options.is_empty() { + continue; + } + // For each option, we call `advance_with_operation_element()` again on + // our own operation element (the field), which gives us some options + // (or not and we bail). + let mut field_options = vec![]; + for implementation_option in &mut implementation_options { + let field_options_for_implementation = implementation_option + .advance_with_operation_element( + supergraph_schema.clone(), + operation_element, + condition_resolver, + )?; + let Some(field_options_for_implementation) = + field_options_for_implementation + else { + continue; + }; + // Advancing a field should never get us into an unsatisfiable + // condition (only fragments can). + if field_options_for_implementation.is_empty() { + return Err(FederationError::internal(format!( + "Unexpected unsatisfiable path after {}", + operation_field + ))); + } + field_options.extend( + field_options_for_implementation + .into_iter() + .map(|s| s.paths), + ); + } + // If we find no options to advance that implementation, we bail (as we + // need to simultaneously advance all implementations). + if field_options.is_empty() { + return Ok((interface_path.map(|p| vec![p.into()]), None)); + }; + options_for_each_implementation.push(field_options); + } + let all_options = SimultaneousPaths::flat_cartesian_product( + options_for_each_implementation, + ); + if let Some(interface_path) = interface_path { + let (interface_path, all_options) = + if direct_path_overrides_type_explosion { + interface_path.mark_overriding(&all_options) + } else { + (interface_path, all_options) + }; + Ok(( + Some( + vec![interface_path.into()] + .into_iter() + .chain(all_options) + .collect(), + ), + None, + )) + } else { + // TODO: This appears to be the only place returning non-None for the + // 2nd argument, so this could be Option<(Vec, bool)> + // instead. + Ok((Some(all_options), Some(true))) + } + } + OutputTypeDefinitionPosition::Union(_) => { + let Some(typename_edge) = self.next_edge_for_field(operation_field) else { + return Err(FederationError::internal( + "Should always have an edge for __typename edge on an union", + )); + }; + let field_path = self.add_field_edge( + operation_field.clone(), + typename_edge, + condition_resolver, + context, + )?; + Ok((field_path.map(|p| vec![p.into()]), None)) + } + _ => { + // Only object, interfaces, and unions (only for __typename) have fields, so + // the query should have been flagged invalid if a field was selected on + // something else. + Err(FederationError::internal(format!( + "Unexpectedly found field {} on non-composite type {}", + operation_field, tail_type_pos, + ))) + } + } + } + OpPathElement::InlineFragment(operation_inline_fragment) => { + let type_condition_name = operation_inline_fragment + .data() + .type_condition_position + .as_ref() + .map(|pos| pos.type_name()) + .unwrap_or_else(|| tail_type_pos.type_name()) + .clone(); + if type_condition_name == *tail_type_pos.type_name() { + // If there is no type condition (or the condition is the type we're already + // on), it means we're essentially just applying some directives (could be a + // `@skip`/`@include` for instance). This doesn't make us take any edge, but if + // the operation element does has directives, we record it. + let fragment_path = if operation_inline_fragment.data().directives.is_empty() { + self.clone() + } else { + self.add( + operation_inline_fragment.clone().into(), + None, + ConditionResolution::no_conditions(), + operation_inline_fragment + .data() + .defer_directive_arguments()?, + )? + }; + return Ok((Some(vec![fragment_path.into()]), None)); + } + match tail_type_pos { + OutputTypeDefinitionPosition::Interface(_) + | OutputTypeDefinitionPosition::Union(_) => { + let tail_type_pos: AbstractTypeDefinitionPosition = + tail_type_pos.clone().try_into()?; + + // If we have an edge for the typecast, take that. + if let Some(edge) = + self.next_edge_for_inline_fragment(operation_inline_fragment) + { + let edge_weight = self.graph.edge_weight(edge)?; + if edge_weight.conditions.is_some() { + return Err(FederationError::internal( + "Unexpectedly found condition on inline fragment collecting edge" + )); + } + let fragment_path = self.add( + operation_inline_fragment.clone().into(), + Some(edge), + ConditionResolution::no_conditions(), + operation_inline_fragment + .data() + .defer_directive_arguments()?, + )?; + return Ok((Some(vec![fragment_path.into()]), None)); + } + + // Otherwise, check what the intersection is between the possible runtime + // types of the tail type and the ones of the typecast. We need to be able + // to go into all those types simultaneously (a.k.a. type explosion). + let from_types = self.runtime_types_of_tail.clone(); + let to_types = supergraph_schema.possible_runtime_types( + supergraph_schema + .get_type(type_condition_name.clone())? + .try_into()?, + )?; + let intersection = from_types.intersection(&to_types); + let mut options_for_each_implementation = vec![]; + for implementation_type_pos in intersection { + let implementation_inline_fragment = + InlineFragment::new(InlineFragmentData { + schema: self + .graph + .schema_by_source(&tail_weight.source)? + .clone(), + parent_type_position: tail_type_pos.clone().into(), + type_condition_position: Some( + implementation_type_pos.clone().into(), + ), + directives: operation_inline_fragment.data().directives.clone(), + selection_id: SelectionId::new(), + }); + let implementation_options = + SimultaneousPathsWithLazyIndirectPaths::new( + self.clone().into(), + context.clone(), + Default::default(), + Default::default(), + ) + .advance_with_operation_element( + supergraph_schema.clone(), + &implementation_inline_fragment.into(), + condition_resolver, + )?; + let Some(implementation_options) = implementation_options else { + return Ok((None, None)); + }; + // If the new inline fragment makes it so that we're on an unsatisfiable + // branch, we just ignore that implementation. + if implementation_options.is_empty() { + continue; + } + options_for_each_implementation.push( + implementation_options + .into_iter() + .map(|s| s.paths) + .collect(), + ) + } + let all_options = SimultaneousPaths::flat_cartesian_product( + options_for_each_implementation, + ); + Ok((Some(all_options), None)) + } + OutputTypeDefinitionPosition::Object(tail_type_pos) => { + // We've already handled the case of a fragment whose type condition is the + // same as the tail type. But the fragment might be for either: + // - A super-type of the tail type. In which case, we're pretty much in the + // same case than if there were no particular type condition. + // - If the tail type is an `@interfaceObject`, then this can be an + // implementation type of the interface in the supergraph. In that case, + // the type condition is not a known type of the subgraph, but the + // subgraph might still be able to handle some of fields, so in that case, + // we essentially "ignore" the fragment for now. We will re-add it back + // later for fields that are not in the current subgraph after we've taken + // an `@key` for the interface. + // - An incompatible type. This can happen for a type that intersects a + // super-type of the tail type (since GraphQL allows a fragment as long as + // there is an intersection). In that case, the whole operation element + // simply cannot ever return anything. + let type_condition_pos = supergraph_schema.get_type(type_condition_name)?; + let abstract_type_condition_pos: Option = + type_condition_pos.clone().try_into().ok(); + if let Some(type_condition_pos) = abstract_type_condition_pos { + if supergraph_schema + .possible_runtime_types(type_condition_pos.clone().into())? + .contains(tail_type_pos) + { + // Type condition is applicable on the tail type, so the types are + // already exploded but the condition can reference types from the + // supergraph that are not present in the local subgraph. + // + // If the operation element has applied directives we need to + // convert it to an inline fragment without type condition, + // otherwise we ignore the fragment altogether. + if operation_inline_fragment.data().directives.is_empty() { + return Ok((Some(vec![self.clone().into()]), None)); + } + let operation_inline_fragment = + InlineFragment::new(InlineFragmentData { + schema: self + .graph + .schema_by_source(&tail_weight.source)? + .clone(), + parent_type_position: tail_type_pos.clone().into(), + type_condition_position: None, + directives: operation_inline_fragment + .data() + .directives + .clone(), + selection_id: SelectionId::new(), + }); + let defer_directive_arguments = operation_inline_fragment + .data() + .defer_directive_arguments()?; + let fragment_path = self.add( + operation_inline_fragment.into(), + None, + ConditionResolution::no_conditions(), + defer_directive_arguments, + )?; + return Ok((Some(vec![fragment_path.into()]), None)); + } + } + + if self.tail_is_interface_object()? { + let mut fake_downcast_edge = None; + for edge in self.next_edges()? { + let edge_weight = self.graph.edge_weight(edge)?; + let QueryGraphEdgeTransition::InterfaceObjectFakeDownCast { + to_type_name, + .. + } = &edge_weight.transition + else { + continue; + }; + if type_condition_pos.type_name() == to_type_name { + fake_downcast_edge = Some(edge); + break; + }; + } + if let Some(fake_downcast_edge) = fake_downcast_edge { + let condition_resolution = self.can_satisfy_conditions( + fake_downcast_edge, + condition_resolver, + context, + &Default::default(), + &Default::default(), + )?; + if matches!( + condition_resolution, + ConditionResolution::Unsatisfied { .. } + ) { + return Ok((None, None)); + } + let fragment_path = self.add( + operation_inline_fragment.clone().into(), + Some(fake_downcast_edge), + condition_resolution, + operation_inline_fragment + .data() + .defer_directive_arguments()?, + )?; + return Ok((Some(vec![fragment_path.into()]), None)); + } + } + + // The operation element we're dealing with can never return results (the + // type conditions applied have no intersection). This means we can fulfill + // this operation element (by doing nothing and returning an empty result), + // which we indicate by return ingan empty list of options. + Ok((Some(vec![]), None)) + } + _ => { + // We shouldn't have a fragment on a non-composite type. + Err(FederationError::internal(format!( + "Unexpectedly found inline fragment {} on non-composite type {}", + operation_inline_fragment, tail_type_pos, + ))) + } + } + } + } + } + + /// Given an `OpGraphPath` and a `SimultaneousPaths` that represent 2 different options to reach + /// the same query leaf field, checks if one can be shown to be always "better" (more + /// efficient/optimal) than the other one, regardless of any surrounding context (i.e. + /// regardless of what the rest of the query plan would be for any other query leaf field). + /// + /// Returns the comparison of the complexity of `self` and `other` (e.g. `Ordering::Less` means + /// `self` is better/has less complexity than `other`). If we can't guarantee anything (at least + /// "out of context"), then we return `Ordering::Equal`. + fn compare_single_vs_multi_path_options_complexity_out_of_context( + &self, + other: &SimultaneousPaths, + ) -> Result { + // This handles the same case as the single-path-only case, but compares the single path + // against each path of the `SimultaneousPaths`, and only "ignores" the `SimultaneousPaths` + // if all its paths can be ignored. + // + // Note that this happens less often than the single-path-only case, but with `@provides` on + // an interface, you can have cases where on one hand you can get something completely on + // the current subgraph, but the type-exploded case has to still be generated due to the + // leaf field not being the one just after the "provided" interface. + for other_path in other.0.iter() { + // Note: Not sure if it is possible for a path of the `SimultaneousPaths` option to + // subsume the single-path one in practice, but if it does, we ignore it because it's + // not obvious that this is enough to get rid of `self` (maybe if `self` is provably a + // bit costlier than one of the paths of `other`, but `other` may have many paths and + // could still be collectively worst than `self`). + if self.compare_single_path_options_complexity_out_of_context(other_path)? + != Ordering::Less + { + return Ok(Ordering::Equal); + } + } + Ok(Ordering::Less) + } + + /// Given 2 `OpGraphPath`s that represent 2 different paths to reach the same query leaf field, + /// checks if one can be shown to be always "better" (more efficient/optimal) than the other + /// one, regardless of any surrounding context (i.e. regardless of what the rest of the query + /// plan would be for any other query leaf field). + /// + /// Returns the comparison of the complexity of `self` and `other` (e.g. `Ordering::Less` means + /// `self` is better/has less complexity than `other`). If we can't guarantee anything (at least + /// "out of context"), then we return `Ordering::Equal`. + fn compare_single_path_options_complexity_out_of_context( + &self, + other: &OpGraphPath, + ) -> Result { + // Currently, this method only handles the case where we have something like: + // - `self`: -[t]-> T(A) -[u]-> U(A) -[x] -> Int(A) + // - `other`: -[t]-> T(A) -[key]-> T(B) -[u]-> U(B) -[x] -> Int(B) + // That is, where we have 2 choices that are identical up to the "end", when one stays in + // the subgraph (`self`, which stays in A) while the other uses a key to get to another + // subgraph (`other`, going to B). + // + // In such a case, whatever else the query plan might be doing, it can never be "worse" + // to use `self` than to use `other` because both will force the same "fetch dependency + // graph node" up to the end, but `other` may force one more fetch that `self` does not. + // Do note that we say "may" above, because the rest of the query plan may very well have a + // forced choice like: + // - `option`: -[t]-> T(A) -[key]-> T(B) -[u]-> U(B) -[y] -> Int(B) + // in which case the query plan will have the jump from A to B after `t` regardless of + // whether we use `self` or `other`, but while in that particular case `self` and `other` + // are about comparable in terms of performance, `self` is still not worse than `other` (and + // in other situations, `self` may be genuinely be better). + // + // Note that this is in many ways just a generalization of a heuristic we use earlier for + // leaf fields. That is, we will never get as input to this method something like: + // - `self`: -[t]-> T(A) -[x] -> Int(A) + // - `other`: -[t]-> T(A) -[key]-> T(B) -[x] -> Int(B) + // because when the code is asked for the options for `x` after ` -[t]-> T(A)`, + // it notices that `x` is a leaf and is in `A`, so it doesn't ever look for alternative + // paths. But this only works for direct leaves of an entity. In the example at the start, + // field `u` makes this not work, because when we compute choices for `u`, we don't yet know + // what comes after that, and so we have to take the option of going to subgraph `B` into + // account (it may very well be that whatever comes after `u` is not in `A`, for instance). + let self_tail_weight = self.graph.node_weight(self.tail)?; + let other_tail_weight = self.graph.node_weight(other.tail)?; + if self_tail_weight.source == other_tail_weight.source { + // As described above, we want to know if one of the paths has no jumps at all (after + // the common prefix) while the other has some. + self.compare_subgraph_jumps_after_last_common_node(other) + } else { + Ok(Ordering::Equal) + } + } +} + +impl Display for OpGraphPath { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + // If the path is length is 0 return "[]" + // Traverse the path, getting the of the edge. + let head = &self.graph.graph()[self.head]; + if head.root_kind.is_some() && self.edges.is_empty() { + return write!(f, "_"); + } + if head.root_kind.is_some() { + write!(f, "{head}")?; + } + self.edges + .iter() + .cloned() + .enumerate() + .try_for_each(|(i, e)| match e { + Some(e) => { + let tail = self.graph.graph().edge_endpoints(e).unwrap().1; + let node = &self.graph.graph()[tail]; + let edge = &self.graph.graph()[e]; + let label = edge.transition.to_string(); + write!(f, " --[{label}]--> {node}") + } + None => write!(f, " ({}) ", self.edge_triggers[i].as_ref()), + })?; + if let Some(label) = self.defer_on_tail.as_ref().and_then(|d| d.label()) { + write!(f, "")?; + } + if !self.runtime_types_of_tail.is_empty() { + write!(f, " (types: [")?; + for ty in self.runtime_types_of_tail.iter() { + write!(f, "{ty}")?; + } + write!(f, "])")?; + } + Ok(()) + } +} + +impl SimultaneousPaths { + /// Given options generated for the advancement of each path of a `SimultaneousPaths`, generate + /// the options for the `SimultaneousPaths` as a whole. + fn flat_cartesian_product( + options_for_each_path: Vec>, + ) -> Vec { + // This can be written more tersely with a bunch of `reduce()`/`flat_map()`s and friends, + // but when interfaces type-explode into many implementations, this can end up with fairly + // large `Vec`s and be a bottleneck, and a more iterative version that pre-allocates `Vec`s + // is quite a bit faster. + if options_for_each_path.is_empty() { + return vec![]; + } + + // Track, for each path, which option index we're at. + let mut option_indexes = vec![0; options_for_each_path.len()]; + + // Pre-allocate `Vec` for the result. + let num_options = options_for_each_path + .iter() + .map(|options| options.len()) + .product(); + let mut product = Vec::with_capacity(num_options); + + // Compute the cartesian product. + for _ in 0..num_options { + let num_simultaneous_paths = options_for_each_path + .iter() + .zip(&option_indexes) + .map(|(options, option_index)| options[*option_index].0.len()) + .sum(); + let mut simultaneous_paths = Vec::with_capacity(num_simultaneous_paths); + + for (options, option_index) in options_for_each_path.iter().zip(&option_indexes) { + simultaneous_paths.extend(options[*option_index].0.iter().cloned()); + } + product.push(SimultaneousPaths(simultaneous_paths)); + + for (options, option_index) in options_for_each_path.iter().zip(&mut option_indexes) { + if *option_index == options.len() - 1 { + *option_index = 0 + } else { + *option_index += 1; + break; + } + } + } + + product + } + + /// Given 2 `SimultaneousPaths` that represent 2 different options to reach the same query leaf + /// field, checks if one can be shown to be always "better" (more efficient/optimal) than the + /// other one, regardless of any surrounding context (i.e. regardless of what the rest of the + /// query plan would be for any other query leaf field). + /// + /// Note that this method is used on the final options of a given "query path", so all the + /// heuristics done within `GraphPath` to avoid unnecessary options have already been applied + /// (e.g. avoiding the consideration of paths that do 2 successive key jumps when there is a + /// 1-jump equivalent), so this focus on what can be done is based on the fact that the path + /// considered is "finished". + /// + /// Returns the comparison of the complexity of `self` and `other` (e.g. `Ordering::Less` means + /// `self` is better/has less complexity than `other`). If we can't guarantee anything (at least + /// "out of context"), then we return `Ordering::Equal`. + fn compare_options_complexity_out_of_context( + &self, + other: &SimultaneousPaths, + ) -> Result { + match (self.0.as_slice(), other.0.as_slice()) { + ([a], [b]) => a.compare_single_path_options_complexity_out_of_context(b), + ([a], _) => a.compare_single_vs_multi_path_options_complexity_out_of_context(other), + (_, [b]) => Ok(b + .compare_single_vs_multi_path_options_complexity_out_of_context(self)? + .reverse()), + _ => Ok(Ordering::Equal), + } + } +} + +impl From> for SimultaneousPaths { + fn from(value: Arc) -> Self { + Self(vec![value]) + } +} + +impl From for SimultaneousPaths { + fn from(value: OpGraphPath) -> Self { + Self::from(Arc::new(value)) + } +} + +impl SimultaneousPathsWithLazyIndirectPaths { + pub(crate) fn new( + paths: SimultaneousPaths, + context: OpGraphPathContext, + excluded_destinations: ExcludedDestinations, + excluded_conditions: ExcludedConditions, + ) -> SimultaneousPathsWithLazyIndirectPaths { + SimultaneousPathsWithLazyIndirectPaths { + lazily_computed_indirect_paths: std::iter::repeat_with(|| None) + .take(paths.0.len()) + .collect(), + paths, + context, + excluded_destinations, + excluded_conditions, + } + } + + /// For a given "input" path (identified by an idx in `paths`), each of its indirect options. + fn indirect_options( + &mut self, + updated_context: &OpGraphPathContext, + path_index: usize, + condition_resolver: &mut impl ConditionResolver, + ) -> Result { + // Note that the provided context will usually be one we had during construction (the + // `updated_context` will be `self.context` updated by whichever operation we're looking at, + // but only operation elements with a @skip/@include will change the context so it's pretty + // rare), which is why we save recomputation by caching the computed value in that case, but + // in case it's different, we compute without caching. + if *updated_context != self.context { + self.compute_indirect_paths(path_index, condition_resolver)?; + } + if let Some(indirect_paths) = &self.lazily_computed_indirect_paths[path_index] { + Ok(indirect_paths.clone()) + } else { + let new_indirect_paths = self.compute_indirect_paths(path_index, condition_resolver)?; + self.lazily_computed_indirect_paths[path_index] = Some(new_indirect_paths.clone()); + Ok(new_indirect_paths) + } + } + + fn compute_indirect_paths( + &self, + path_index: usize, + condition_resolver: &mut impl ConditionResolver, + ) -> Result { + self.paths.0[path_index].advance_with_non_collecting_and_type_preserving_transitions( + &self.context, + condition_resolver, + &self.excluded_destinations, + &self.excluded_conditions, + // The transitions taken by this method are non-collecting transitions, in which case + // the trigger is the context (which is really a hack to provide context information for + // keys during fetch dependency graph updating). + |_, context| OpGraphPathTrigger::Context(context.clone()), + |graph, node, trigger| graph.edge_for_op_graph_path_trigger(node, trigger), + ) + } + + fn create_lazy_options( + &self, + options: Vec, + context: OpGraphPathContext, + ) -> Vec { + options + .into_iter() + .map(|paths| { + SimultaneousPathsWithLazyIndirectPaths::new( + paths, + context.clone(), + self.excluded_destinations.clone(), + self.excluded_conditions.clone(), + ) + }) + .collect() + } + + /// Returns `None` if the operation cannot be dealt with/advanced. Otherwise, it returns a `Vec` + /// of options we can be in after advancing the operation, each option being a set of + /// simultaneous paths in the subgraphs (a single path in the simple case, but type exploding + /// may make us explore multiple paths simultaneously). + /// + /// The lists of options can be empty, which has the special meaning that the operation is + /// guaranteed to have no results (it corresponds to unsatisfiable conditions), meaning that as + /// far as query planning goes, we can just ignore the operation but otherwise continue. + // PORT_NOTE: In the JS codebase, this was named `advanceSimultaneousPathsWithOperation`. + pub(crate) fn advance_with_operation_element( + &mut self, + supergraph_schema: ValidFederationSchema, + operation_element: &OpPathElement, + condition_resolver: &mut impl ConditionResolver, + ) -> Result>, FederationError> { + let updated_context = self.context.with_context_of(operation_element)?; + let mut options_for_each_path = vec![]; + + // To call the mutating method `indirect_options()`, we need to not hold any immutable + // references to `self`, which means cloning these paths when iterating. + let paths = self.paths.0.clone(); + for (path_index, path) in paths.iter().enumerate() { + let mut options = None; + let should_reenter_subgraph = path.defer_on_tail.is_some() + && matches!(operation_element, OpPathElement::Field(_)); + if !should_reenter_subgraph { + let (advance_options, has_only_type_exploded_results) = path + .advance_with_operation_element( + supergraph_schema.clone(), + operation_element, + &updated_context, + condition_resolver, + )?; + // If we've got some options, there are a number of cases where there is no point + // looking for indirect paths: + // - If the operation element is terminal: this means we just found a direct edge + // that is terminal, so no indirect options could be better (this is not true for + // non-terminal operation element, where the direct route may end up being a dead + // end later). One exception however is when `advanceWithOperationElement()` + // type-exploded (meaning that we're on an interface), because in that case, the + // type-exploded options have already taken indirect edges into account, so it's + // possible that an indirect edge _from the interface_ could be better, but only + // if there wasn't a "true" direct edge on the interface, which is what + // `has_only_type_exploded_results` tells us. + // - If we get options, but an empty set of them, which signifies the operation + // element corresponds to unsatisfiable conditions and we can essentially ignore + // it. + // - If the operation element is a fragment in general: if we were able to find a + // direct option, that means the type is known in the "current" subgraph, and so + // we'll still be able to take any indirect edges that we could take now later, + // for the follow-up operation element. And pushing the decision will give us more + // context and may avoid a bunch of state explosion in practice. + if let Some(advance_options) = advance_options { + if advance_options.is_empty() + || (operation_element.is_terminal()? + && !has_only_type_exploded_results.unwrap_or(false)) + || matches!(operation_element, OpPathElement::InlineFragment(_)) + { + // Note that if options is empty, that means this particular "branch" is + // unsatisfiable, so we should just ignore it. + if !advance_options.is_empty() { + options_for_each_path.push(advance_options); + } + continue; + } else { + options = Some(advance_options); + } + } + } + + // If there was not a valid direct path (or we didn't check those because we entered a + // defer), that's ok, we'll just try with non-collecting edges. + let mut options = options.unwrap_or_else(Vec::new); + if let OpPathElement::Field(operation_field) = operation_element { + // Add whatever options can be obtained by taking some non-collecting edges first. + let paths_with_non_collecting_edges = self + .indirect_options(&updated_context, path_index, condition_resolver)? + .filter_non_collecting_paths_for_field(operation_field)?; + if !paths_with_non_collecting_edges.paths.is_empty() { + for paths_with_non_collecting_edges in + paths_with_non_collecting_edges.paths.iter() + { + let (advance_options, _) = paths_with_non_collecting_edges + .advance_with_operation_element( + supergraph_schema.clone(), + operation_element, + &updated_context, + condition_resolver, + )?; + // If we can't advance the operation element after that path, ignore it, + // it's just not an option. + let Some(advance_options) = advance_options else { + continue; + }; + // `advance_with_operation_element()` can return an empty `Vec` only if the + // operation element is a fragment with a type condition that, on top of the + // "current" type is unsatisfiable. But as we've only taken type-preserving + // transitions, we cannot get an empty result at this point if we didn't get + // one when testing direct transitions above (in which case we would have + // exited the method early). + if advance_options.is_empty() { + return Err(FederationError::internal(format!( + "Unexpected empty options after non-collecting path {} for {}", + paths_with_non_collecting_edges, operation_element, + ))); + } + // There is a special case we can deal with now. Namely, suppose we have a + // case where a query is reaching an interface I in a subgraph S1, we query + // some field of that interface x, and say that x is provided in subgraph S2 + // but by an @interfaceObject for I. + // + // As we look for direct options for I.x in S1 initially, we won't find `x`, + // so we will try to type-explode I (let's say into implementations A and + // B). And in some cases doing so is necessary, but it may also lead to the + // type-exploding option to look like: + // [ + // I(S1) -[... on A]-> A(S1) -[key]-> I(S2) -[x] -> Int(S2), + // I(S1) -[... on B]-> B(S1) -[key]-> I(S2) -[x] -> Int(S2), + // ] + // But as we look at indirect options now (still from I in S1), we will note + // that we can also do: + // I(S1) -[key]-> I(S2) -[x] -> Int(S2), + // And while both options are technically valid, the new one really subsumes + // the first one: there is no point in type-exploding to take a key to the + // same exact subgraph if using the key on the interface directly works. + // + // So here, we look for that case and remove any type-exploding option that + // the new path renders unnecessary. Do note that we only make that check + // when the new option is a single-path option, because this gets kind of + // complicated otherwise. + if paths_with_non_collecting_edges.tail_is_interface_object()? { + for indirect_option in &advance_options { + if indirect_option.0.len() == 1 { + let mut new_options = vec![]; + for option in options { + let mut is_equivalent = true; + for path in &option.0 { + is_equivalent = is_equivalent + && indirect_option.0[0] + .is_equivalent_save_for_type_explosion_to( + path, + )?; + } + if !is_equivalent { + new_options.push(option) + } + } + options = new_options; + } + } + } + options.extend(advance_options); + } + } + } + + // If we were entering a @defer, we've skipped the potential "direct" options because we + // need an "indirect" one (a key/root query) to be able to actually defer. But in rare + // cases, it's possible we actually couldn't resolve the key fields needed to take a key + // but could still find a direct path. If so, it means it's a corner case where we + // cannot do query-planner-based-@defer and have to fall back on not deferring. + if options.is_empty() && should_reenter_subgraph { + let (advance_options, _) = path.advance_with_operation_element( + supergraph_schema.clone(), + operation_element, + &updated_context, + condition_resolver, + )?; + options = advance_options.unwrap_or_else(Vec::new); + } + + // At this point, if options is empty, it means we found no ways to advance the + // operation element for this path, so we should return `None`. + if options.is_empty() { + return Ok(None); + } else { + options_for_each_path.push(options); + } + } + + let all_options = SimultaneousPaths::flat_cartesian_product(options_for_each_path); + Ok(Some(self.create_lazy_options(all_options, updated_context))) + } +} + +// PORT_NOTE: JS passes a ConditionResolver here, we do not: see port note for +// `SimultaneousPathsWithLazyIndirectPaths` +// TODO(@goto-bus-stop): JS passes `override_conditions` here and maintains stores +// references to it in the created paths. AFAICT override conditions +// are shared mutable state among different query graphs, so having references to +// it in many structures would require synchronization. We should likely pass it as +// an argument to exactly the functionality that uses it. +pub fn create_initial_options( + initial_path: GraphPath>, + initial_type: &QueryGraphNodeType, + initial_context: OpGraphPathContext, + condition_resolver: &mut impl ConditionResolver, + excluded_edges: ExcludedDestinations, + excluded_conditions: ExcludedConditions, +) -> Result, FederationError> { + let initial_paths = SimultaneousPaths::from(initial_path); + let mut lazy_initial_path = SimultaneousPathsWithLazyIndirectPaths::new( + initial_paths, + initial_context.clone(), + excluded_edges, + excluded_conditions, + ); + + if initial_type.is_federated_root_type() { + let initial_options = + lazy_initial_path.indirect_options(&initial_context, 0, condition_resolver)?; + let options = initial_options + .paths + .iter() + .cloned() + .map(SimultaneousPaths::from) + .collect(); + Ok(lazy_initial_path.create_lazy_options(options, initial_context)) + } else { + Ok(vec![lazy_initial_path]) + } +} + +impl ClosedBranch { + /// This method is called on a closed branch (i.e. on all the possible options found to get a + /// particular leaf of the query being planned), and when there is more than one option, it + /// tries a last effort at checking an option can be shown to be less efficient than another one + /// _whatever the rest of the query plan is_ (that is, whatever the options for any other leaf + /// of the query are). + /// + /// In practice, this compares all pairs of options and calls the heuristics of + /// `compare_options_complexity_out_of_context()` on them to see if one strictly subsumes the + /// other (and if that's the case, the subsumed one is ignored). + pub(crate) fn maybe_eliminate_strictly_more_costly_paths( + self, + ) -> Result { + if self.0.len() <= 1 { + return Ok(self); + } + + // Keep track of which options should be kept. + let mut keep_options = vec![true; self.0.len()]; + for option_index in 0..(self.0.len()) { + if !keep_options[option_index] { + continue; + } + // We compare the current option to every other remaining option. + // + // PORT_NOTE: We don't technically need to iterate in reverse order here, but the JS + // codebase does, and we do the same to ensure the result is the same. (The JS codebase + // requires this because it removes from the array it's iterating through.) + let option = &self.0[option_index]; + let mut keep_option = true; + for (other_option, keep_other_option) in self.0[(option_index + 1)..] + .iter() + .zip(&mut keep_options[(option_index + 1)..]) + .rev() + { + if !*keep_other_option { + continue; + } + match option + .paths + .compare_options_complexity_out_of_context(&other_option.paths)? + { + Ordering::Less => { + *keep_other_option = false; + } + Ordering::Equal => {} + Ordering::Greater => { + keep_option = false; + break; + } + } + } + if !keep_option { + keep_options[option_index] = false; + } + } + + Ok(ClosedBranch( + self.0 + .into_iter() + .zip(&keep_options) + .filter(|(_, &keep_option)| keep_option) + .map(|(option, _)| option) + .collect(), + )) + } +} + +impl OpPath { + pub fn len(&self) -> usize { + self.0.len() + } + + pub(crate) fn is_empty(&self) -> bool { + self.0.is_empty() + } + + pub(crate) fn strip_prefix(&self, maybe_prefix: &Self) -> Option { + self.0 + .strip_prefix(&*maybe_prefix.0) + .map(|slice| Self(slice.to_vec())) + } + + pub(crate) fn with_pushed(&self, element: Arc) -> Self { + let mut new = self.0.clone(); + new.push(element); + Self(new) + } +} + +impl TryFrom<&'_ OpPath> for Vec { + type Error = FederationError; + + fn try_from(value: &'_ OpPath) -> Result { + value + .0 + .iter() + .map(|path_element| { + Ok(match path_element.as_ref() { + OpPathElement::Field(field) => QueryPathElement::Field(field.try_into()?), + OpPathElement::InlineFragment(inline) => { + QueryPathElement::InlineFragment(inline.try_into()?) + } + }) + }) + .collect() + } +} + +#[cfg(test)] +mod tests { + use std::sync::Arc; + + use apollo_compiler::executable::DirectiveList; + use apollo_compiler::schema::Name; + use apollo_compiler::NodeStr; + use apollo_compiler::Schema; + use petgraph::stable_graph::EdgeIndex; + use petgraph::stable_graph::NodeIndex; + + use crate::query_graph::build_query_graph::build_query_graph; + use crate::query_graph::condition_resolver::ConditionResolution; + use crate::query_graph::graph_path::OpGraphPath; + use crate::query_graph::graph_path::OpGraphPathTrigger; + use crate::query_graph::graph_path::OpPathElement; + use crate::query_plan::operation::Field; + use crate::query_plan::operation::FieldData; + use crate::schema::position::FieldDefinitionPosition; + use crate::schema::position::ObjectFieldDefinitionPosition; + use crate::schema::ValidFederationSchema; + + #[test] + fn path_display() { + let src = r#" + type Query + { + t: T + } + + type T + { + otherId: ID! + id: ID! + } + "#; + let schema = Schema::parse_and_validate(src, "./").unwrap(); + let schema = ValidFederationSchema::new(schema).unwrap(); + let name = NodeStr::new("S1"); + let graph = build_query_graph(name, schema.clone()).unwrap(); + let path = OpGraphPath::new(Arc::new(graph), NodeIndex::new(0)).unwrap(); + assert_eq!(path.to_string(), "_"); + let pos = ObjectFieldDefinitionPosition { + type_name: Name::new("T").unwrap(), + field_name: Name::new("t").unwrap(), + }; + let data = FieldData { + schema: schema.clone(), + field_position: FieldDefinitionPosition::Object(pos), + alias: None, + arguments: Arc::new(Vec::new()), + directives: Arc::new(DirectiveList::new()), + sibling_typename: None, + }; + let trigger = OpGraphPathTrigger::OpPathElement(OpPathElement::Field(Field::new(data))); + let path = path + .add( + trigger, + Some(EdgeIndex::new(3)), + ConditionResolution::Satisfied { + cost: 0, + path_tree: None, + }, + None, + ) + .unwrap(); + assert_eq!(path.to_string(), "Query(S1)* --[t]--> T(S1) (types: [T])"); + let pos = ObjectFieldDefinitionPosition { + type_name: Name::new("ID").unwrap(), + field_name: Name::new("id").unwrap(), + }; + let data = FieldData { + schema, + field_position: FieldDefinitionPosition::Object(pos), + alias: None, + arguments: Arc::new(Vec::new()), + directives: Arc::new(DirectiveList::new()), + sibling_typename: None, + }; + let trigger = OpGraphPathTrigger::OpPathElement(OpPathElement::Field(Field::new(data))); + let path = path + .add( + trigger, + Some(EdgeIndex::new(1)), + ConditionResolution::Satisfied { + cost: 0, + path_tree: None, + }, + None, + ) + .unwrap(); + assert_eq!( + path.to_string(), + "Query(S1)* --[t]--> T(S1) --[id]--> ID(S1)" + ); + } +} diff --git a/apollo-federation/src/query_graph/mod.rs b/apollo-federation/src/query_graph/mod.rs new file mode 100644 index 0000000000..dd29e5acff --- /dev/null +++ b/apollo-federation/src/query_graph/mod.rs @@ -0,0 +1,838 @@ +use std::fmt::Display; +use std::fmt::Formatter; +use std::hash::Hash; +use std::sync::Arc; + +use apollo_compiler::schema::Name; +use apollo_compiler::schema::NamedType; +use apollo_compiler::NodeStr; +use indexmap::IndexMap; +use indexmap::IndexSet; +use petgraph::graph::DiGraph; +use petgraph::graph::EdgeIndex; +use petgraph::graph::EdgeReference; +use petgraph::graph::NodeIndex; +use petgraph::visit::EdgeRef; +use petgraph::Direction; + +use crate::error::FederationError; +use crate::error::SingleFederationError; +use crate::query_plan::operation::Field; +use crate::query_plan::operation::InlineFragment; +use crate::query_plan::operation::SelectionSet; +use crate::schema::field_set::parse_field_set; +use crate::schema::position::CompositeTypeDefinitionPosition; +use crate::schema::position::FieldDefinitionPosition; +use crate::schema::position::InterfaceFieldDefinitionPosition; +use crate::schema::position::ObjectTypeDefinitionPosition; +use crate::schema::position::OutputTypeDefinitionPosition; +use crate::schema::position::SchemaRootDefinitionKind; +use crate::schema::ValidFederationSchema; + +pub mod build_query_graph; +pub(crate) mod condition_resolver; +pub(crate) mod extract_subgraphs_from_supergraph; +pub(crate) mod graph_path; +pub mod output; +pub(crate) mod path_tree; + +pub use build_query_graph::build_federated_query_graph; + +use crate::query_graph::condition_resolver::ConditionResolution; +use crate::query_graph::condition_resolver::ConditionResolver; +use crate::query_graph::graph_path::ExcludedConditions; +use crate::query_graph::graph_path::ExcludedDestinations; +use crate::query_graph::graph_path::OpGraphPathContext; +use crate::query_graph::graph_path::OpGraphPathTrigger; +use crate::query_graph::graph_path::OpPathElement; +use crate::query_plan::QueryPlanCost; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub(crate) struct QueryGraphNode { + /// The GraphQL type this node points to. + pub(crate) type_: QueryGraphNodeType, + /// An identifier of the underlying schema containing the `type_` this node points to. This is + /// mainly used in federated query graphs, where the `source` is a subgraph name. + pub(crate) source: NodeStr, + /// True if there is a cross-subgraph edge that is reachable from this node. + pub(crate) has_reachable_cross_subgraph_edges: bool, + /// @provides works by creating duplicates of the node/type involved in the provides and adding + /// the provided edges only to those copies. This means that with @provides, you can have more + /// than one node per-type-and-subgraph in a query graph. Which is fine, but this `provide_id` + /// allows distinguishing if a node was created as part of this @provides duplication or not. + /// The value of this field has no other meaning than to be unique per-@provide, and so all the + /// nodes copied for a given @provides application will have the same `provide_id`. Overall, + /// this mostly exists for debugging visualization. + pub(crate) provide_id: Option, + // If present, this node represents a root node of the corresponding kind. + pub(crate) root_kind: Option, +} + +impl QueryGraphNode { + pub fn is_root_node(&self) -> bool { + matches!(self.type_, QueryGraphNodeType::FederatedRootType(_)) + } +} + +impl Display for QueryGraphNode { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}({})", self.type_, self.source)?; + if let Some(provide_id) = self.provide_id { + write!(f, "-{}", provide_id)?; + } + if self.root_kind.is_some() { + write!(f, "*")?; + } + Ok(()) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, derive_more::From, derive_more::IsVariant)] +pub(crate) enum QueryGraphNodeType { + SchemaType(OutputTypeDefinitionPosition), + FederatedRootType(SchemaRootDefinitionKind), +} + +impl Display for QueryGraphNodeType { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + QueryGraphNodeType::SchemaType(pos) => pos.fmt(f), + QueryGraphNodeType::FederatedRootType(root_kind) => { + write!(f, "[{root_kind}]") + } + } + } +} + +#[derive(Debug, Clone)] +pub(crate) struct QueryGraphEdge { + /// Indicates what kind of edge this is and what the edge does/represents. For instance, if the + /// edge represents a field, the `transition` will be a `FieldCollection` transition and will + /// link to the definition of the field it represents. + pub(crate) transition: QueryGraphEdgeTransition, + /// Optional conditions on an edge. + /// + /// Conditions are a select of selections (in the GraphQL sense) that the traversal of a query + /// graph needs to "collect" (traverse edges with transitions corresponding to those selections) + /// in order to be able to collect that edge. + /// + /// Conditions are primarily used for edges corresponding to @key, in which case they correspond + /// to the fields composing the @key. In other words, for an @key edge, conditions basically + /// represent the fact that you need the key to be able to use an @key edge. + /// + /// Outside of keys, @requires edges also rely on conditions. + pub(crate) conditions: Option>, +} + +impl Display for QueryGraphEdge { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + if matches!( + self.transition, + QueryGraphEdgeTransition::SubgraphEnteringTransition + ) && self.conditions.is_none() + { + return Ok(()); + } + if let Some(conditions) = &self.conditions { + write!(f, "{} ⊢ {}", conditions, self.transition) + } else { + self.transition.fmt(f) + } + } +} + +/// The type of query graph edge "transition". +/// +/// An edge transition encodes what the edge corresponds to, in the underlying GraphQL schema. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub(crate) enum QueryGraphEdgeTransition { + /// A field edge, going from (a node for) the field parent type to the field's (base) type. + FieldCollection { + /// The name of the schema containing the field. + source: NodeStr, + /// The object/interface field being collected. + field_definition_position: FieldDefinitionPosition, + /// Whether this field is part of an @provides. + is_part_of_provides: bool, + }, + /// A downcast edge, going from a composite type (object, interface, or union) to another + /// composite type that intersects that type (i.e. has at least one possible runtime object type + /// in common with it). + Downcast { + /// The name of the schema containing the from/to types. + source: NodeStr, + /// The parent type of the type condition, i.e. the type of the selection set containing + /// the type condition. + from_type_position: CompositeTypeDefinitionPosition, + /// The type of the type condition, i.e. the type coming after "... on". + to_type_position: CompositeTypeDefinitionPosition, + }, + /// A key edge (only found in federated query graphs) going from an entity type in a particular + /// subgraph to the same entity type but in another subgraph. Key transition edges _must_ have + /// `conditions` corresponding to the key fields. + KeyResolution, + /// A root type edge (only found in federated query graphs) going from a root type (query, + /// mutation or subscription) of a subgraph to the (same) root type of another subgraph. It + /// encodes the fact that if a subgraph field returns a root type, any subgraph can be queried + /// from there. + RootTypeResolution { + /// The kind of schema root resolved. + root_kind: SchemaRootDefinitionKind, + }, + /// A subgraph-entering edge, which is a special case only used for edges coming out of the root + /// nodes of "federated" query graphs. It does not correspond to any physical GraphQL elements + /// but can be understood as the fact that the router is always free to start querying any of + /// the subgraph services as needed. + SubgraphEnteringTransition, + /// A "fake" downcast edge (only found in federated query graphs) going from an @interfaceObject + /// type to an implementation. This encodes the fact that an @interfaceObject type "stands-in" + /// for any possible implementations (in the supergraph) of the corresponding interface. It is + /// "fake" because the corresponding edge stays on the @interfaceObject type (this is also why + /// the "to type" is only a name: that to/casted type does not actually exist in the subgraph + /// in which the corresponding edge will be found). + InterfaceObjectFakeDownCast { + /// The name of the schema containing the from type. + source: NodeStr, + /// The parent type of the type condition, i.e. the type of the selection set containing + /// the type condition. + from_type_position: CompositeTypeDefinitionPosition, + /// The type of the type condition, i.e. the type coming after "... on". + to_type_name: Name, + }, +} + +impl QueryGraphEdgeTransition { + pub(crate) fn collect_operation_elements(&self) -> bool { + match self { + QueryGraphEdgeTransition::FieldCollection { .. } => true, + QueryGraphEdgeTransition::Downcast { .. } => true, + QueryGraphEdgeTransition::KeyResolution => false, + QueryGraphEdgeTransition::RootTypeResolution { .. } => false, + QueryGraphEdgeTransition::SubgraphEnteringTransition => false, + QueryGraphEdgeTransition::InterfaceObjectFakeDownCast { .. } => true, + } + } +} + +impl Display for QueryGraphEdgeTransition { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + QueryGraphEdgeTransition::FieldCollection { + field_definition_position, + .. + } => { + write!(f, "{}", field_definition_position.field_name()) + } + QueryGraphEdgeTransition::Downcast { + to_type_position, .. + } => { + write!(f, "... on {}", to_type_position.type_name()) + } + QueryGraphEdgeTransition::KeyResolution => { + write!(f, "key()") + } + QueryGraphEdgeTransition::RootTypeResolution { root_kind } => { + write!(f, "{}()", root_kind) + } + QueryGraphEdgeTransition::SubgraphEnteringTransition => { + write!(f, "∅") + } + QueryGraphEdgeTransition::InterfaceObjectFakeDownCast { to_type_name, .. } => { + write!(f, "... on {}", to_type_name) + } + } + } +} + +#[derive(Debug)] +pub struct QueryGraph { + /// The "current" source of the query graph. For query graphs representing a single source + /// graph, this will only ever be one value, but it will change for "federated" query graphs + /// while they're being built (and after construction, will become FEDERATED_GRAPH_ROOT_SOURCE, + /// which is a reserved placeholder value). + current_source: NodeStr, + /// The nodes/edges of the query graph. Note that nodes/edges should never be removed, so + /// indexes are immutable when a node/edge is created. + graph: DiGraph, + /// The sources on which the query graph was built, which is a set (potentially of size 1) of + /// GraphQL schema keyed by the name identifying them. Note that the `source` strings in the + /// nodes/edges of a query graph are guaranteed to be valid key in this map. + pub(crate) sources: IndexMap, + /// A map (keyed by source) that associates type names of the underlying schema on which this + /// query graph was built to each of the nodes that points to a type of that name. Note that for + /// a "federated" query graph source, each type name will only map to a single node. + types_to_nodes_by_source: IndexMap>>, + /// A map (keyed by source) that associates schema root kinds to root nodes. + root_kinds_to_nodes_by_source: IndexMap>, + /// Maps an edge to the possible edges that can follow it "productively", that is without + /// creating a trivially inefficient path. + /// + /// More precisely, this map is equivalent to looking at the out edges of a given edge's tail + /// node and filtering those edges that "never make sense" after the given edge, which mainly + /// amounts to avoiding chaining @key edges when we know there is guaranteed to be a better + /// option. As an example, suppose we have 3 subgraphs A, B and C which all defined an + /// `@key(fields: "id")` on some entity type `T`. Then it is never interesting to take that @key + /// edge from B -> C after A -> B because if we're in A and want to get to C, we can always do + /// A -> C (of course, this is only true because it's the "same" key). + /// + /// See `precompute_non_trivial_followup_edges` for more details on which exact edges are + /// filtered. + /// + /// Lastly, note that the main reason for having this field is that its result is pre-computed. + /// Which in turn is done for performance reasons: having the same key defined in multiple + /// subgraphs is _the_ most common pattern, and while our later algorithms (composition + /// validation and query planning) would know to not select those trivially inefficient + /// "detours", they might have to redo those checks many times and pre-computing once it is + /// significantly faster (and pretty easy). FWIW, when originally introduced, this optimization + /// lowered composition validation on a big composition (100+ subgraphs) from ~4 minutes to + /// ~10 seconds. + non_trivial_followup_edges: IndexMap>, +} + +impl QueryGraph { + pub(crate) fn name(&self) -> &str { + &self.current_source + } + + pub(crate) fn graph(&self) -> &DiGraph { + &self.graph + } + + pub(crate) fn node_weight(&self, node: NodeIndex) -> Result<&QueryGraphNode, FederationError> { + self.graph.node_weight(node).ok_or_else(|| { + SingleFederationError::Internal { + message: "Node unexpectedly missing".to_owned(), + } + .into() + }) + } + + fn node_weight_mut(&mut self, node: NodeIndex) -> Result<&mut QueryGraphNode, FederationError> { + self.graph.node_weight_mut(node).ok_or_else(|| { + SingleFederationError::Internal { + message: "Node unexpectedly missing".to_owned(), + } + .into() + }) + } + + pub(crate) fn edge_weight(&self, edge: EdgeIndex) -> Result<&QueryGraphEdge, FederationError> { + self.graph.edge_weight(edge).ok_or_else(|| { + SingleFederationError::Internal { + message: "Edge unexpectedly missing".to_owned(), + } + .into() + }) + } + + fn edge_weight_mut(&mut self, edge: EdgeIndex) -> Result<&mut QueryGraphEdge, FederationError> { + self.graph.edge_weight_mut(edge).ok_or_else(|| { + SingleFederationError::Internal { + message: "Edge unexpectedly missing".to_owned(), + } + .into() + }) + } + + pub(crate) fn edge_endpoints( + &self, + edge: EdgeIndex, + ) -> Result<(NodeIndex, NodeIndex), FederationError> { + self.graph.edge_endpoints(edge).ok_or_else(|| { + SingleFederationError::Internal { + message: "Edge unexpectedly missing".to_owned(), + } + .into() + }) + } + + pub(crate) fn schema(&self) -> Result<&ValidFederationSchema, FederationError> { + self.schema_by_source(&self.current_source) + } + + pub(crate) fn schema_by_source( + &self, + source: &str, + ) -> Result<&ValidFederationSchema, FederationError> { + self.sources.get(source).ok_or_else(|| { + SingleFederationError::Internal { + message: "Schema unexpectedly missing".to_owned(), + } + .into() + }) + } + + pub(crate) fn sources(&self) -> impl Iterator { + self.sources.iter() + } + + /// Returns an iterator over of node indices whose name matches the given type name. + pub(crate) fn nodes_for_type<'c, 'b: 'c, 'a: 'c>( + &'a self, + name: &'b Name, + ) -> impl 'c + Iterator { + self.types_to_nodes_by_source + .values() + .filter_map(|tys| tys.get(name)) + .flat_map(|vs| vs.iter().cloned()) + } + + pub(crate) fn types_to_nodes( + &self, + ) -> Result<&IndexMap>, FederationError> { + self.types_to_nodes_by_source(&self.current_source) + } + + fn types_to_nodes_by_source( + &self, + source: &str, + ) -> Result<&IndexMap>, FederationError> { + self.types_to_nodes_by_source.get(source).ok_or_else(|| { + SingleFederationError::Internal { + message: "Types-to-nodes map unexpectedly missing".to_owned(), + } + .into() + }) + } + + fn types_to_nodes_mut( + &mut self, + ) -> Result<&mut IndexMap>, FederationError> { + self.types_to_nodes_by_source + .get_mut(&self.current_source) + .ok_or_else(|| { + SingleFederationError::Internal { + message: "Types-to-nodes map unexpectedly missing".to_owned(), + } + .into() + }) + } + + pub(crate) fn root_kinds_to_nodes( + &self, + ) -> Result<&IndexMap, FederationError> { + self.root_kinds_to_nodes_by_source + .get(&self.current_source) + .ok_or_else(|| { + SingleFederationError::Internal { + message: "Root-kinds-to-nodes map unexpectedly missing".to_owned(), + } + .into() + }) + } + + pub(crate) fn root_kinds_to_nodes_by_source( + &self, + source: &str, + ) -> Result<&IndexMap, FederationError> { + self.root_kinds_to_nodes_by_source + .get(source) + .ok_or_else(|| { + SingleFederationError::Internal { + message: "Root-kinds-to-nodes map unexpectedly missing".to_owned(), + } + .into() + }) + } + + fn root_kinds_to_nodes_mut( + &mut self, + ) -> Result<&mut IndexMap, FederationError> { + self.root_kinds_to_nodes_by_source + .get_mut(&self.current_source) + .ok_or_else(|| { + SingleFederationError::Internal { + message: "Root-kinds-to-nodes map unexpectedly missing".to_owned(), + } + .into() + }) + } + + pub(crate) fn non_trivial_followup_edges(&self) -> &IndexMap> { + &self.non_trivial_followup_edges + } + + /// All outward edges from the given node (including self-key and self-root-type-resolution + /// edges). Primarily used by `@defer`, when needing to re-enter a subgraph for a deferred + /// section. + pub(crate) fn out_edges_with_federation_self_edges( + &self, + node: NodeIndex, + ) -> Vec> { + Self::sorted_edges(self.graph.edges_directed(node, Direction::Outgoing)) + } + + /// The outward edges from the given node, minus self-key and self-root-type-resolution edges, + /// as they're rarely useful (currently only used by `@defer`). + pub(crate) fn out_edges(&self, node: NodeIndex) -> Vec> { + Self::sorted_edges(self.graph.edges_directed(node, Direction::Outgoing).filter( + |edge_ref| { + !(edge_ref.source() == edge_ref.target() + && matches!( + edge_ref.weight().transition, + QueryGraphEdgeTransition::KeyResolution + | QueryGraphEdgeTransition::RootTypeResolution { .. } + )) + }, + )) + } + + /// Edge iteration order is unspecified in petgraph, but appears to be + /// *reverse* insertion order in practice. + /// This can affect generated query plans, such as when two options have the same cost. + /// To match the JS code base, we want to iterate in insertion order. + /// + /// Sorting by edge indices relies on documented behavior: + /// + /// + /// As of this writing, edges of the query graph are removed + /// in `FederatedQueryGraphBuilder::update_edge_tail` which specifically preserves indices + /// by pairing with an insertion. + fn sorted_edges<'graph>( + edges: impl Iterator>, + ) -> Vec> { + let mut edges: Vec<_> = edges.collect(); + edges.sort_by_key(|e| -> EdgeIndex { e.id() }); + edges + } + + pub(crate) fn is_self_key_or_root_edge( + &self, + edge: EdgeIndex, + ) -> Result { + let edge_weight = self.edge_weight(edge)?; + let (head, tail) = self.edge_endpoints(edge)?; + let head_weight = self.node_weight(head)?; + let tail_weight = self.node_weight(tail)?; + Ok(head_weight.source == tail_weight.source + && matches!( + edge_weight.transition, + QueryGraphEdgeTransition::KeyResolution + | QueryGraphEdgeTransition::RootTypeResolution { .. } + )) + } + + // PORT_NOTE: In the JS codebase, this was named `hasValidDirectKeyEdge`. + pub(crate) fn has_satisfiable_direct_key_edge( + &self, + from_node: NodeIndex, + to_subgraph: &str, + condition_resolver: &mut impl ConditionResolver, + max_cost: QueryPlanCost, + ) -> Result { + for edge_ref in self.out_edges(from_node) { + let edge_weight = edge_ref.weight(); + if !matches!( + edge_weight.transition, + QueryGraphEdgeTransition::KeyResolution + ) { + continue; + } + + let tail = edge_ref.target(); + let tail_weight = self.node_weight(tail)?; + if tail_weight.source != to_subgraph { + continue; + } + + let condition_resolution = condition_resolver.resolve( + edge_ref.id(), + &OpGraphPathContext::default(), + &ExcludedDestinations::default(), + &ExcludedConditions::default(), + )?; + let ConditionResolution::Satisfied { cost, .. } = condition_resolution else { + continue; + }; + + // During composition validation, we consider all conditions to have cost 1. + if cost <= max_cost { + return Ok(true); + } + } + Ok(false) + } + + pub(crate) fn edge_for_field(&self, node: NodeIndex, field: &Field) -> Option { + let mut candidates = self.out_edges(node).into_iter().filter_map(|edge_ref| { + let edge_weight = edge_ref.weight(); + let QueryGraphEdgeTransition::FieldCollection { + field_definition_position, + .. + } = &edge_weight.transition + else { + return None; + }; + // We explicitly avoid comparing parent type's here, to allow interface object + // fields to match operation fields with the same name but differing types. + if field.data().field_position.field_name() == field_definition_position.field_name() { + Some(edge_ref.id()) + } else { + None + } + }); + if let Some(candidate) = candidates.next() { + // PORT_NOTE: The JS codebase used an assertion rather than a debug assertion here. We + // consider it unlikely for there to be more than one candidate given all the code paths + // that create edges, so we've downgraded this to a debug assertion. + debug_assert!( + candidates.next().is_none(), + "Unexpectedly found multiple candidates", + ); + Some(candidate) + } else { + None + } + } + + pub(crate) fn edge_for_inline_fragment( + &self, + node: NodeIndex, + inline_fragment: &InlineFragment, + ) -> Option { + let Some(type_condition_pos) = &inline_fragment.data().type_condition_position else { + // No type condition means the type hasn't changed, meaning there is no edge to take. + return None; + }; + let mut candidates = self.out_edges(node).into_iter().filter_map(|edge_ref| { + let edge_weight = edge_ref.weight(); + let QueryGraphEdgeTransition::Downcast { + to_type_position, .. + } = &edge_weight.transition + else { + return None; + }; + // We explicitly avoid comparing type kinds, to allow interface object types to + // match operation inline fragments (where the supergraph type kind is interface, + // but the subgraph type kind is object). + if type_condition_pos.type_name() == to_type_position.type_name() { + Some(edge_ref.id()) + } else { + None + } + }); + if let Some(candidate) = candidates.next() { + // PORT_NOTE: The JS codebase used an assertion rather than a debug assertion here. We + // consider it unlikely for there to be more than one candidate given all the code paths + // that create edges, so we've downgraded this to a debug assertion. + debug_assert!( + candidates.next().is_none(), + "Unexpectedly found multiple candidates", + ); + Some(candidate) + } else { + None + } + } + + pub(crate) fn edge_for_op_graph_path_trigger( + &self, + node: NodeIndex, + op_graph_path_trigger: &OpGraphPathTrigger, + ) -> Option> { + let OpGraphPathTrigger::OpPathElement(op_path_element) = op_graph_path_trigger else { + return None; + }; + match op_path_element { + OpPathElement::Field(field) => self.edge_for_field(node, field).map(Some), + OpPathElement::InlineFragment(inline_fragment) => { + if inline_fragment.data().type_condition_position.is_some() { + self.edge_for_inline_fragment(node, inline_fragment) + .map(Some) + } else { + Some(None) + } + } + } + } + + /// Given the possible runtime types at the head of the given edge, returns the possible runtime + /// types after traversing the edge. + // PORT_NOTE: Named `updateRuntimeTypes` in the JS codebase. + pub(crate) fn advance_possible_runtime_types( + &self, + possible_runtime_types: &IndexSet, + edge: Option, + ) -> Result, FederationError> { + let Some(edge) = edge else { + return Ok(possible_runtime_types.clone()); + }; + + let edge_weight = self.edge_weight(edge)?; + let (_, tail) = self.edge_endpoints(edge)?; + let tail_weight = self.node_weight(tail)?; + let QueryGraphNodeType::SchemaType(tail_type_pos) = &tail_weight.type_ else { + return Err(FederationError::internal( + "Unexpectedly encountered federation root node as tail node.", + )); + }; + return match &edge_weight.transition { + QueryGraphEdgeTransition::FieldCollection { + source, + field_definition_position, + .. + } => { + let Ok(_): Result = + tail_type_pos.clone().try_into() + else { + return Ok(IndexSet::new()); + }; + let schema = self.schema_by_source(source)?; + let mut new_possible_runtime_types = IndexSet::new(); + for possible_runtime_type in possible_runtime_types { + let field_pos = + possible_runtime_type.field(field_definition_position.field_name().clone()); + let Some(field) = field_pos.try_get(schema.schema()) else { + continue; + }; + let field_type_pos: CompositeTypeDefinitionPosition = schema + .get_type(field.ty.inner_named_type().clone())? + .try_into()?; + new_possible_runtime_types + .extend(schema.possible_runtime_types(field_type_pos)?); + } + Ok(new_possible_runtime_types) + } + QueryGraphEdgeTransition::Downcast { + source, + to_type_position, + .. + } => Ok(self + .schema_by_source(source)? + .possible_runtime_types(to_type_position.clone())? + .intersection(possible_runtime_types) + .cloned() + .collect()), + QueryGraphEdgeTransition::KeyResolution => { + let tail_type_pos: CompositeTypeDefinitionPosition = + tail_type_pos.clone().try_into()?; + Ok(self + .schema_by_source(&tail_weight.source)? + .possible_runtime_types(tail_type_pos)?) + } + QueryGraphEdgeTransition::RootTypeResolution { .. } => { + let OutputTypeDefinitionPosition::Object(tail_type_pos) = tail_type_pos.clone() + else { + return Err(FederationError::internal( + "Unexpectedly encountered non-object root operation type.", + )); + }; + Ok(IndexSet::from([tail_type_pos])) + } + QueryGraphEdgeTransition::SubgraphEnteringTransition => { + let OutputTypeDefinitionPosition::Object(tail_type_pos) = tail_type_pos.clone() + else { + return Err(FederationError::internal( + "Unexpectedly encountered non-object root operation type.", + )); + }; + Ok(IndexSet::from([tail_type_pos])) + } + QueryGraphEdgeTransition::InterfaceObjectFakeDownCast { .. } => { + Ok(possible_runtime_types.clone()) + } + }; + } + + /// Returns a selection set that can be used as a key for the given type, and that can be + /// entirely resolved in the same subgraph. Returns None if such a key does not exist for the + /// given type. + pub(crate) fn get_locally_satisfiable_key( + &self, + node_index: NodeIndex, + ) -> Result, FederationError> { + let node = self.node_weight(node_index)?; + let type_name = match &node.type_ { + QueryGraphNodeType::SchemaType(ty) => { + CompositeTypeDefinitionPosition::try_from(ty.clone())? + } + QueryGraphNodeType::FederatedRootType(_) => { + return Err(FederationError::internal(format!( + "get_locally_satisfiable_key must be called on a composite type, got {}", + node.type_ + ))); + } + }; + let schema = self.schema_by_source(&node.source)?; + let Some(metadata) = schema.subgraph_metadata() else { + return Err(FederationError::internal(format!( + "Could not find subgraph metadata for source {}", + node.source + ))); + }; + let key_directive_definition = metadata + .federation_spec_definition() + .key_directive_definition(schema)?; + + let ty = type_name.get(schema.schema())?; + + for key in ty.directives().get_all(&key_directive_definition.name) { + let Some(value) = key + .argument_by_name("fields") + .and_then(|arg| arg.as_node_str()) + .cloned() + else { + continue; + }; + let selection = parse_field_set(schema, ty.name().clone(), &value)?; + let has_external = metadata + .external_metadata() + .selects_any_external_field(&selection)?; + if !has_external { + return Ok(Some(selection)); + } + } + + Ok(None) + } + + pub(crate) fn is_cross_subgraph_edge(&self, edge: EdgeIndex) -> Result { + let (head, tail) = self.edge_endpoints(edge)?; + let head_weight = self.node_weight(head)?; + let tail_weight = self.node_weight(tail)?; + Ok(head_weight.source != tail_weight.source) + } + + pub(crate) fn is_provides_edge(&self, edge: EdgeIndex) -> Result { + let edge_weight = self.edge_weight(edge)?; + let QueryGraphEdgeTransition::FieldCollection { + is_part_of_provides, + .. + } = &edge_weight.transition + else { + return Ok(false); + }; + Ok(*is_part_of_provides) + } + + pub(crate) fn has_an_implementation_with_provides( + &self, + source: &NodeStr, + interface_field_definition_position: InterfaceFieldDefinitionPosition, + ) -> Result { + let schema = self.schema_by_source(source)?; + let Some(metadata) = schema.subgraph_metadata() else { + return Err(FederationError::internal(format!( + "Interface should have come from a federation subgraph {}", + source + ))); + }; + + let provides_directive_definition = metadata + .federation_spec_definition() + .provides_directive_definition(schema)?; + + for object_type_definition_position in + schema.possible_runtime_types(interface_field_definition_position.parent().into())? + { + let field_pos = object_type_definition_position + .field(interface_field_definition_position.field_name.clone()); + let field = field_pos.get(schema.schema())?; + if field.directives.has(&provides_directive_definition.name) { + return Ok(true); + } + } + + Ok(false) + } +} diff --git a/apollo-federation/src/query_graph/output.rs b/apollo-federation/src/query_graph/output.rs new file mode 100644 index 0000000000..515979441c --- /dev/null +++ b/apollo-federation/src/query_graph/output.rs @@ -0,0 +1,153 @@ +// Output module for query graphs +// - Corresponds to the `graphviz` and `mermaid` modules from the JS federation. + +use std::fmt::Write; + +use apollo_compiler::NodeStr; +use petgraph::dot::Config; +use petgraph::dot::Dot; +use petgraph::graph::DiGraph; +use petgraph::graph::EdgeIndex; +use petgraph::stable_graph::StableGraph; + +use crate::query_graph::QueryGraph; +use crate::query_graph::QueryGraphEdge; +use crate::query_graph::QueryGraphNode; + +type InnerGraph = DiGraph; +type StableInnerGraph = StableGraph; + +////////////////////////////////////////////////////////////////////////////// +// GraphViz output for QueryGraph + +fn label_edge(edge: &QueryGraphEdge) -> String { + let label = edge.to_string(); + if label.is_empty() { + String::new() + } else { + format!("label=\"{}\"", edge) + } +} + +fn label_node(node: &QueryGraphNode) -> String { + format!("label=\"{}\"", node.type_) +} + +pub fn to_dot(graph: &QueryGraph) -> String { + if graph.sources.len() > 1 { + return to_dot_federated(graph).expect("Failed to generate the federated graph"); + } + + // Note: Use label_edge/label_node as `attr_getters` in order to create custom label + // strings, instead of the default labeling. + let config = [Config::NodeNoLabel, Config::EdgeNoLabel]; + Dot::with_attr_getters( + &graph.graph, + &config, + &(|_, er| label_edge(er.weight())), + &(|_, (_, node)| label_node(node)), + ) + .to_string() +} + +fn to_dot_federated(graph: &QueryGraph) -> Result { + fn edge_within_cluster( + graph: &StableInnerGraph, + cluster_name: &NodeStr, + edge_index: EdgeIndex, + ) -> bool { + graph.edge_endpoints(edge_index).is_some_and(|(n1, n2)| { + graph[n1].source == *cluster_name && graph[n2].source == *cluster_name + }) + } + + fn edge_across_clusters(graph: &StableInnerGraph, edge_index: EdgeIndex) -> bool { + graph + .edge_endpoints(edge_index) + .is_some_and(|(n1, n2)| graph[n1].source != graph[n2].source) + } + + fn label_cluster_node(node: &QueryGraphNode) -> String { + let provide_id = match node.provide_id { + Some(id) => format!("#{}", id), + None => String::new(), + }; + format!(r#"label="{}{}@{}""#, node.type_, provide_id, node.source) + } + + // Build a stable graph, so we can derive subgraph clusters with the same indices. + let stable_graph = StableGraph::from(graph.graph.clone()); + let cluster_dot_config = [ + Config::NodeNoLabel, + Config::EdgeNoLabel, + Config::GraphContentOnly, + ]; + + let mut dot_str = String::new(); + writeln!(dot_str, r#"digraph "{}" {{"#, graph.name())?; + + // Subgraph clusters + for (cluster_name, _) in graph.sources.iter() { + if cluster_name == graph.name() { + continue; // skip non-subgraph nodes + } + + let filtered_graph: StableInnerGraph = stable_graph.filter_map( + |_i, n| { + if n.source == *cluster_name { + Some(n.clone()) + } else { + None + } + }, + |i, e| { + if edge_within_cluster(&stable_graph, cluster_name, i) { + Some(e.clone()) + } else { + None + } + }, + ); + let s = Dot::with_attr_getters( + &filtered_graph, + &cluster_dot_config, + &(|_, er| label_edge(er.weight())), + &(|_, (_, node)| label_cluster_node(node)), + ) + .to_string(); + + writeln!(dot_str, r#" subgraph "cluster_{}" {{"#, &cluster_name)?; + writeln!(dot_str, r#" label = "Subgraph \"{}\"";"#, &cluster_name)?; + writeln!(dot_str, r#" color = "black";"#)?; + writeln!(dot_str, r#" style = "";"#)?; + dot_str.push_str(&s); + writeln!(dot_str, " }}")?; + } + + // Supergraph nodes + for i in stable_graph.node_indices() { + let node = &stable_graph[i]; + if node.source == graph.name() { + writeln!(dot_str, " {} [{}]", i.index(), label_node(node))?; + } + } + + // Supergraph edges + for i in stable_graph.edge_indices() { + if edge_across_clusters(&stable_graph, i) { + if let Some((n1, n2)) = stable_graph.edge_endpoints(i) { + let edge = &stable_graph[i]; + writeln!( + dot_str, + " {} -> {} [{}]", + n1.index(), + n2.index(), + label_edge(edge) + )?; + } + } + } + + writeln!(dot_str, "}}")?; + Ok(dot_str) +} diff --git a/apollo-federation/src/query_graph/path_tree.rs b/apollo-federation/src/query_graph/path_tree.rs new file mode 100644 index 0000000000..2cbe28bc6d --- /dev/null +++ b/apollo-federation/src/query_graph/path_tree.rs @@ -0,0 +1,548 @@ +use std::fmt::Display; +use std::fmt::Formatter; +use std::hash::Hash; +use std::sync::Arc; + +use apollo_compiler::NodeStr; +use indexmap::map::Entry; +use indexmap::IndexMap; +use petgraph::graph::EdgeIndex; +use petgraph::graph::NodeIndex; + +use crate::error::FederationError; +use crate::query_graph::graph_path::GraphPathItem; +use crate::query_graph::graph_path::OpGraphPath; +use crate::query_graph::graph_path::OpGraphPathTrigger; +use crate::query_graph::QueryGraph; +use crate::query_graph::QueryGraphNode; +use crate::query_plan::operation::SelectionSet; + +/// A "merged" tree representation for a vector of `GraphPath`s that start at a common query graph +/// node, in which each node of the tree corresponds to a node in the query graph, and a tree's node +/// has a child for every unique pair of edge and trigger. +// PORT_NOTE: The JS codebase additionally has a property `triggerEquality`; this existed because +// Typescript doesn't have a native way of associating equality/hash functions with types, so they +// were passed around manually. This isn't the case with Rust, where we instead implement trigger +// equality via `PartialEq` and `Hash`. +#[derive(Clone)] +pub(crate) struct PathTree +where + TTrigger: Eq + Hash, + TEdge: Copy + Into>, +{ + /// The query graph of which this is a path tree. + pub(crate) graph: Arc, + /// The query graph node at which the path tree starts. + pub(crate) node: NodeIndex, + /// Note that `ClosedPath`s have an optimization which splits them into paths and a selection + /// set representing a trailing query to a single subgraph at the final nodes of the paths. For + /// such paths where this `PathTree`'s node corresponds to that final node, those selection sets + /// are collected here. This is really an optimization to avoid unnecessary merging of selection + /// sets when they query a single subgraph. + pub(crate) local_selection_sets: Vec>, + /// The child `PathTree`s for this `PathTree` node. There is a child for every unique pair of + /// edge and trigger present at this particular sub-path within the `GraphPath`s covered by this + /// `PathTree` node. + pub(crate) childs: Vec>>, +} + +#[derive(Debug)] +pub(crate) struct PathTreeChild +where + TTrigger: Eq + Hash, + TEdge: Copy + Into>, +{ + /// The edge connecting this child to its parent. + pub(crate) edge: TEdge, + /// The trigger for the edge connecting this child to its parent. + pub(crate) trigger: Arc, + /// The conditions required to be fetched if this edge is taken. + pub(crate) conditions: Option>, + /// The child `PathTree` reached by taking the edge. + pub(crate) tree: Arc>, +} + +/// A `PathTree` whose triggers are operation elements (essentially meaning that the constituent +/// `GraphPath`s were guided by a GraphQL operation). +pub(crate) type OpPathTree = PathTree>; + +impl OpPathTree { + pub(crate) fn new(graph: Arc, node: NodeIndex) -> Self { + Self { + graph, + node, + local_selection_sets: Vec::new(), + childs: Vec::new(), + } + } + + pub(crate) fn from_op_paths( + graph: Arc, + node: NodeIndex, + paths: &[(&OpGraphPath, Option<&Arc>)], + ) -> Result { + assert!( + !paths.is_empty(), + "OpPathTree cannot be created from an empty set of paths" + ); + Self::from_paths( + graph, + node, + paths + .iter() + .map(|(path, selections)| (path.iter(), *selections)) + .collect::>(), + ) + } + + pub(crate) fn is_leaf(&self) -> bool { + self.childs.is_empty() + } + + pub(crate) fn is_all_in_same_subgraph(&self) -> Result { + let node_weight = self.graph.node_weight(self.node)?; + self.is_all_in_same_subgraph_internal(&node_weight.source) + } + + fn is_all_in_same_subgraph_internal(&self, target: &NodeStr) -> Result { + let node_weight = self.graph.node_weight(self.node)?; + if node_weight.source != *target { + return Ok(false); + } + for child in &self.childs { + if !child.tree.is_all_in_same_subgraph_internal(target)? { + return Ok(false); + } + } + Ok(true) + } + + fn fmt_internal( + &self, + f: &mut Formatter<'_>, + indent: &str, + include_conditions: bool, + ) -> std::fmt::Result { + if self.is_leaf() { + return write!(f, "{}", self.vertex()); + } + write!(f, "{}:", self.vertex())?; + let child_indent = format!("{indent} "); + for child in self.childs.iter() { + let index = child.edge.unwrap_or_else(EdgeIndex::end); + write!(f, "\n{indent} -> [{}] ", index.index())?; + if include_conditions { + if let Some(ref child_cond) = child.conditions { + write!(f, "!! {{\n{indent} ")?; + child_cond.fmt_internal(f, &child_indent, /*include_conditions*/ true)?; + write!(f, "\n{indent} }}")?; + } + } + write!(f, "{} = ", child.trigger)?; + child + .tree + .fmt_internal(f, &child_indent, include_conditions)?; + } + Ok(()) + } +} + +impl Display for OpPathTree { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let indent = "".to_owned(); // Empty indent at the root level + self.fmt_internal(f, &indent, /*include_conditions*/ false) + } +} + +impl PathTree +where + TTrigger: Eq + Hash, + TEdge: Copy + Hash + Eq + Into>, +{ + /// Returns the `QueryGraphNode` represented by `self.node`. + /// PORT_NOTE: This is named after the JS implementation's `vertex` field. + /// But, it may make sense to rename it once porting is over. + pub(crate) fn vertex(&self) -> &QueryGraphNode { + self.graph.node_weight(self.node).unwrap() + } + + fn from_paths<'inputs>( + graph: Arc, + node: NodeIndex, + graph_paths_and_selections: Vec<( + impl Iterator>, + Option<&'inputs Arc>, + )>, + ) -> Result + where + TTrigger: 'inputs, + TEdge: 'inputs, + { + // Group by and order by unique edge ID, and among those by unique trigger + let mut merged = IndexMap::>::new(); + + struct ByUniqueEdge<'inputs, TTrigger, GraphPathIter> { + target_node: NodeIndex, + by_unique_trigger: + IndexMap<&'inputs Arc, PathTreeChildInputs<'inputs, GraphPathIter>>, + } + + struct PathTreeChildInputs<'inputs, GraphPathIter> { + conditions: Option>, + sub_paths_and_selections: Vec<(GraphPathIter, Option<&'inputs Arc>)>, + } + + let mut local_selection_sets = Vec::new(); + + for (mut graph_path_iter, selection) in graph_paths_and_selections { + let Some((generic_edge, trigger, conditions)) = graph_path_iter.next() else { + // End of an input `GraphPath` + if let Some(selection) = selection { + local_selection_sets.push(selection.clone()); + } + continue; + }; + let for_edge = match merged.entry(generic_edge) { + Entry::Occupied(entry) => entry.into_mut(), + Entry::Vacant(entry) => { + entry.insert(ByUniqueEdge { + target_node: if let Some(edge) = generic_edge.into() { + let (_source, target) = graph.edge_endpoints(edge)?; + target + } else { + // For a "None" edge, stay on the same node + node + }, + by_unique_trigger: IndexMap::new(), + }) + } + }; + match for_edge.by_unique_trigger.entry(trigger) { + Entry::Occupied(entry) => { + let existing = entry.into_mut(); + existing.conditions = merge_conditions(&existing.conditions, conditions); + existing + .sub_paths_and_selections + .push((graph_path_iter, selection)) + // Note that as we merge, we don't create a new child + } + Entry::Vacant(entry) => { + entry.insert(PathTreeChildInputs { + conditions: conditions.clone(), + sub_paths_and_selections: vec![(graph_path_iter, selection)], + }); + } + } + } + + let mut childs = Vec::new(); + for (edge, by_unique_edge) in merged { + for (trigger, child) in by_unique_edge.by_unique_trigger { + childs.push(Arc::new(PathTreeChild { + edge, + trigger: trigger.clone(), + conditions: child.conditions.clone(), + tree: Arc::new(Self::from_paths( + graph.clone(), + by_unique_edge.target_node, + child.sub_paths_and_selections, + )?), + })) + } + } + Ok(Self { + graph, + node, + local_selection_sets, + childs, + }) + } + + fn merge_if_not_equal(self: &Arc, other: &Arc) -> Arc { + if self.equals_same_root(other) { + self.clone() + } else { + self.merge(other) + } + } + + /// May have false negatives (see comment about `Arc::ptr_eq`) + fn equals_same_root(self: &Arc, other: &Arc) -> bool { + Arc::ptr_eq(self, other) + || self.childs.iter().zip(&other.childs).all(|(a, b)| { + a.edge == b.edge + // `Arc::ptr_eq` instead of `==` is faster and good enough. + // This method is all about avoid unnecessary merging + // when we suspect conditions trees have been build from the exact same inputs. + && Arc::ptr_eq(&a.trigger, &b.trigger) + && match (&a.conditions, &b.conditions) { + (None, None) => true, + (Some(cond_a), Some(cond_b)) => cond_a.equals_same_root(cond_b), + _ => false, + } + && a.tree.equals_same_root(&b.tree) + }) + } + + pub(crate) fn merge(self: &Arc, other: &Arc) -> Arc { + if Arc::ptr_eq(self, other) { + return self.clone(); + } + assert!( + Arc::ptr_eq(&self.graph, &other.graph), + "Cannot merge path tree build on another graph" + ); + assert_eq!( + self.node, other.node, + "Cannot merge path trees rooted different nodes" + ); + if other.childs.is_empty() { + return self.clone(); + } + if self.childs.is_empty() { + return other.clone(); + } + + let mut count_to_add = 0; + let merge_indices: Vec<_> = other + .childs + .iter() + .map(|other_child| { + let position = self.childs.iter().position(|self_child| { + self_child.edge == other_child.edge && self_child.trigger == other_child.trigger + }); + if position.is_none() { + count_to_add += 1 + } + position + }) + .collect(); + let expected_new_len = self.childs.len() + count_to_add; + let mut childs = Vec::with_capacity(expected_new_len); + childs.extend(self.childs.iter().cloned()); + for (other_child, merge_index) in other.childs.iter().zip(merge_indices) { + if let Some(i) = merge_index { + let child = &mut childs[i]; + *child = Arc::new(PathTreeChild { + edge: child.edge, + trigger: child.trigger.clone(), + conditions: merge_conditions(&child.conditions, &other_child.conditions), + tree: child.tree.merge(&other_child.tree), + }) + } else { + childs.push(other_child.clone()) + } + } + assert_eq!(childs.len(), expected_new_len); + + Arc::new(Self { + graph: self.graph.clone(), + node: self.node, + local_selection_sets: self + .local_selection_sets + .iter() + .chain(&other.local_selection_sets) + .cloned() + .collect(), + childs, + }) + } +} + +fn merge_conditions( + a: &Option>, + b: &Option>, +) -> Option> { + match (a, b) { + (Some(a), Some(b)) => Some(a.merge_if_not_equal(b)), + (Some(a), None) => Some(a.clone()), + (None, Some(b)) => Some(b.clone()), + (None, None) => None, + } +} + +impl std::fmt::Debug + for PathTree +where + TTrigger: Eq + Hash, + TEdge: Copy + Into>, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let Self { + graph: _, // skip + node, + local_selection_sets, + childs, + } = self; + f.debug_struct("PathTree") + .field("node", node) + .field("local_selection_sets", local_selection_sets) + .field("childs", childs) + .finish() + } +} + +#[cfg(test)] +mod tests { + use std::sync::Arc; + + use apollo_compiler::executable::DirectiveList; + use apollo_compiler::ExecutableDocument; + use petgraph::stable_graph::NodeIndex; + use petgraph::visit::EdgeRef; + + use crate::error::FederationError; + use crate::query_graph::build_query_graph::build_query_graph; + use crate::query_graph::condition_resolver::ConditionResolution; + use crate::query_graph::graph_path::OpGraphPath; + use crate::query_graph::graph_path::OpGraphPathTrigger; + use crate::query_graph::graph_path::OpPathElement; + use crate::query_graph::path_tree::OpPathTree; + use crate::query_graph::QueryGraph; + use crate::query_graph::QueryGraphEdgeTransition; + use crate::query_plan::operation::normalize_operation; + use crate::query_plan::operation::Field; + use crate::query_plan::operation::FieldData; + use crate::schema::position::SchemaRootDefinitionKind; + use crate::schema::ValidFederationSchema; + + // NB: stole from operation.rs + fn parse_schema_and_operation( + schema_and_operation: &str, + ) -> (ValidFederationSchema, ExecutableDocument) { + let (schema, executable_document) = + apollo_compiler::parse_mixed_validate(schema_and_operation, "document.graphql") + .unwrap(); + let executable_document = executable_document.into_inner(); + let schema = ValidFederationSchema::new(schema).unwrap(); + (schema, executable_document) + } + + fn trivial_condition() -> ConditionResolution { + ConditionResolution::Satisfied { + cost: 0, + path_tree: None, + } + } + + // A helper function that builds a graph path from a sequence of field names + fn build_graph_path( + query_graph: &Arc, + op_kind: SchemaRootDefinitionKind, + path: &[&str], + ) -> Result { + let nodes_by_kind = query_graph.root_kinds_to_nodes()?; + let root_node_idx = nodes_by_kind[&op_kind]; + let mut graph_path = OpGraphPath::new(query_graph.clone(), root_node_idx)?; + let mut curr_node_idx = root_node_idx; + for field_name in path.iter() { + // find the edge that matches `field_name` + let (edge_ref, field_def) = query_graph + .out_edges(curr_node_idx) + .into_iter() + .find_map(|e_ref| { + let edge = e_ref.weight(); + match &edge.transition { + QueryGraphEdgeTransition::FieldCollection { + field_definition_position, + .. + } => { + if field_definition_position.field_name() == *field_name { + Some((e_ref, field_definition_position)) + } else { + None + } + } + + _ => None, + } + }) + .unwrap(); + + // build the trigger for the edge + let data = FieldData { + schema: query_graph.schema().unwrap().clone(), + field_position: field_def.clone(), + alias: None, + arguments: Arc::new(Vec::new()), + directives: Arc::new(DirectiveList::new()), + sibling_typename: None, + }; + let trigger = OpGraphPathTrigger::OpPathElement(OpPathElement::Field(Field::new(data))); + + // add the edge to the path + graph_path = graph_path + .add(trigger, Some(edge_ref.id()), trivial_condition(), None) + .unwrap(); + + // prepare for the next iteration + curr_node_idx = edge_ref.target(); + } + Ok(graph_path) + } + + #[test] + fn path_tree_display() { + let src = r#" + type Query + { + t: T + } + + type T + { + otherId: ID! + id: ID! + } + + query Test + { + t { + id + } + } + "#; + + let (schema, mut executable_document) = parse_schema_and_operation(src); + let (op_name, operation) = executable_document.named_operations.first_mut().unwrap(); + + let query_graph = + Arc::new(build_query_graph(op_name.to_string().into(), schema.clone()).unwrap()); + + let path1 = + build_graph_path(&query_graph, SchemaRootDefinitionKind::Query, &["t", "id"]).unwrap(); + assert_eq!( + path1.to_string(), + "Query(Test)* --[t]--> T(Test) --[id]--> ID(Test)" + ); + + let path2 = build_graph_path( + &query_graph, + SchemaRootDefinitionKind::Query, + &["t", "otherId"], + ) + .unwrap(); + assert_eq!( + path2.to_string(), + "Query(Test)* --[t]--> T(Test) --[otherId]--> ID(Test)" + ); + + let normalized_operation = + normalize_operation(operation, Default::default(), &schema, &Default::default()) + .unwrap(); + let selection_set = Arc::new(normalized_operation.selection_set); + + let paths = vec![ + (&path1, Some(&selection_set)), + (&path2, Some(&selection_set)), + ]; + let path_tree = + OpPathTree::from_op_paths(query_graph.to_owned(), NodeIndex::new(0), &paths).unwrap(); + let computed = path_tree.to_string(); + let expected = r#"Query(Test)*: + -> [3] t = T(Test): + -> [1] id = ID(Test) + -> [0] otherId = ID(Test)"#; + assert_eq!(computed, expected); + } +} diff --git a/apollo-federation/src/query_plan/conditions.rs b/apollo-federation/src/query_plan/conditions.rs new file mode 100644 index 0000000000..0f7ad26cbc --- /dev/null +++ b/apollo-federation/src/query_plan/conditions.rs @@ -0,0 +1,301 @@ +use std::sync::Arc; + +use apollo_compiler::ast::Directive; +use apollo_compiler::executable::DirectiveList; +use apollo_compiler::executable::Name; +use apollo_compiler::executable::Value; +use indexmap::map::Entry; +use indexmap::IndexMap; + +use crate::error::FederationError; +use crate::query_graph::graph_path::OpPathElement; +use crate::query_plan::operation::Selection; +use crate::query_plan::operation::SelectionMap; +use crate::query_plan::operation::SelectionSet; + +/// This struct is meant for tracking whether a selection set in a `FetchDependencyGraphNode` needs +/// to be queried, based on the `@skip`/`@include` applications on the selections within. +/// Accordingly, there is much logic around merging and short-circuiting; `OperationConditional` is +/// the more appropriate struct when trying to record the original structure/intent of those +/// `@skip`/`@include` applications. +#[derive(Debug, Clone, PartialEq)] +pub(crate) enum Conditions { + Variables(VariableConditions), + Boolean(bool), +} + +#[derive(Debug, Clone, PartialEq)] +pub(crate) enum Condition { + Variable(VariableCondition), + Boolean(bool), +} + +/// A list of variable conditions, represented as a map from variable names to whether that variable +/// is negated in the condition. We maintain the invariant that there's at least one condition (i.e. +/// the map is non-empty), and that there's at most one condition per variable name. +#[derive(Debug, Clone, PartialEq)] +pub(crate) struct VariableConditions(Arc>); + +impl VariableConditions { + /// Construct VariableConditions from a non-empty map of variable names. + /// + /// In release builds, this does not check if the map is empty. + fn new_unchecked(map: IndexMap) -> Self { + debug_assert!(!map.is_empty()); + Self(Arc::new(map)) + } + + pub fn insert(&mut self, name: Name, negated: bool) { + Arc::make_mut(&mut self.0).insert(name, negated); + } + + /// Returns true if there are no conditions. + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + /// Returns a variable condition by name. + pub fn get(&self, name: &str) -> Option { + self.0.get_key_value(name).map(|(variable, &negated)| { + let variable = variable.clone(); + VariableCondition { variable, negated } + }) + } + + /// Returns whether a variable condition is negated, or None if there is no condition for the variable name. + pub fn is_negated(&self, name: &str) -> Option { + self.0.get(name).copied() + } + + pub fn iter(&self) -> impl Iterator { + self.0.iter().map(|(name, &negated)| (name, negated)) + } +} + +#[derive(Debug, Clone, PartialEq)] +pub(crate) struct VariableCondition { + variable: Name, + negated: bool, +} + +impl Conditions { + /// Create conditions from a map of variable conditions. If empty, instead returns a + /// condition that always evaluates to true. + fn from_variables(map: IndexMap) -> Self { + if map.is_empty() { + Self::Boolean(true) + } else { + Self::Variables(VariableConditions::new_unchecked(map)) + } + } + + pub(crate) fn from_directives(directives: &DirectiveList) -> Result { + let mut variables = IndexMap::new(); + for directive in directives { + let negated = match directive.name.as_str() { + "include" => false, + "skip" => true, + _ => continue, + }; + let value = directive.argument_by_name("if").ok_or_else(|| { + FederationError::internal(format!( + "missing if argument on @{}", + if negated { "skip" } else { "include" }, + )) + })?; + match &**value { + Value::Boolean(false) if !negated => return Ok(Self::Boolean(false)), + Value::Boolean(true) if negated => return Ok(Self::Boolean(false)), + Value::Boolean(_) => {} + Value::Variable(name) => match variables.entry(name.clone()) { + Entry::Occupied(entry) => { + let previous_negated = *entry.get(); + if previous_negated != negated { + return Ok(Self::Boolean(false)); + } + } + Entry::Vacant(entry) => { + entry.insert(negated); + } + }, + _ => { + return Err(FederationError::internal(format!( + "expected boolean or variable `if` argument, got {value}", + ))) + } + } + } + Ok(Self::from_variables(variables)) + } + + pub(crate) fn update_with(&self, new_conditions: &Self) -> Self { + match (new_conditions, self) { + (Conditions::Boolean(_), _) | (_, Conditions::Boolean(_)) => new_conditions.clone(), + (Conditions::Variables(new_conditions), Conditions::Variables(handled_conditions)) => { + let mut filtered = IndexMap::new(); + for (cond_name, &cond_negated) in new_conditions.0.iter() { + match handled_conditions.is_negated(cond_name) { + Some(handled_cond) if cond_negated != handled_cond => { + // If we've already handled that exact condition, we can skip it. + // But if we've already handled the _negation_ of this condition, then this mean the overall conditions + // are unreachable and we can just return `false` directly. + return Conditions::Boolean(false); + } + Some(_) => {} + None => { + filtered.insert(cond_name.clone(), cond_negated); + } + } + } + Self::from_variables(filtered) + } + } + } + + pub(crate) fn merge(self, other: Self) -> Self { + match (self, other) { + // Absorbing element + (Conditions::Boolean(false), _) | (_, Conditions::Boolean(false)) => { + Conditions::Boolean(false) + } + + // Neutral element + (Conditions::Boolean(true), x) | (x, Conditions::Boolean(true)) => x, + + (Conditions::Variables(mut self_vars), Conditions::Variables(other_vars)) => { + let vars = Arc::make_mut(&mut self_vars.0); + for (name, other_negated) in other_vars.0.iter() { + match vars.entry(name.clone()) { + Entry::Occupied(entry) => { + let self_negated = entry.get(); + if self_negated != other_negated { + return Conditions::Boolean(false); + } + } + Entry::Vacant(entry) => { + entry.insert(*other_negated); + } + } + } + Conditions::Variables(self_vars) + } + } + } +} + +fn is_constant_condition(condition: &Conditions) -> bool { + match condition { + Conditions::Variables(_) => false, + Conditions::Boolean(_) => true, + } +} + +pub(crate) fn remove_conditions_from_selection_set( + selection_set: &SelectionSet, + conditions: &Conditions, +) -> Result { + match conditions { + Conditions::Boolean(_) => { + // If the conditions are the constant false, this means we know the selection will not be included + // in the plan in practice, and it doesn't matter too much what we return here. So we just + // the input unchanged as a shortcut. + // If the conditions are the constant true, then it means we have no conditions to remove and we can + // keep the selection "as is". + Ok(selection_set.clone()) + } + Conditions::Variables(variable_conditions) => { + let mut selection_map = SelectionMap::new(); + + for selection in selection_set.selections.values() { + let element = selection.element()?; + // We remove any of the conditions on the element and recurse. + let updated_element = + remove_conditions_of_element(element.clone(), variable_conditions); + let new_selection = if let Ok(Some(selection_set)) = selection.selection_set() { + let updated_selection_set = + remove_conditions_from_selection_set(selection_set, conditions)?; + if updated_element == element { + if *selection_set == updated_selection_set { + selection.clone() + } else { + selection.with_updated_selection_set(Some(updated_selection_set))? + } + } else { + Selection::from_element(updated_element, Some(updated_selection_set))? + } + } else if updated_element == element { + selection.clone() + } else { + Selection::from_element(updated_element, None)? + }; + selection_map.insert(new_selection); + } + + Ok(SelectionSet { + schema: selection_set.schema.clone(), + type_position: selection_set.type_position.clone(), + selections: Arc::new(selection_map), + }) + } + } +} + +fn remove_conditions_of_element( + element: OpPathElement, + conditions: &VariableConditions, +) -> OpPathElement { + let updated_directives: DirectiveList = DirectiveList( + element + .directives() + .iter() + .filter(|d| { + !matches_condition_for_kind(d, conditions, ConditionKind::Include) + && !matches_condition_for_kind(d, conditions, ConditionKind::Skip) + }) + .cloned() + .collect(), + ); + + if updated_directives.0.len() == element.directives().len() { + element + } else { + element.with_updated_directives(updated_directives) + } +} + +#[derive(PartialEq)] +enum ConditionKind { + Include, + Skip, +} + +fn matches_condition_for_kind( + directive: &Directive, + conditions: &VariableConditions, + kind: ConditionKind, +) -> bool { + let kind_str = match kind { + ConditionKind::Include => "include", + ConditionKind::Skip => "skip", + }; + + if directive.name != kind_str { + return false; + } + + let value = directive.argument_by_name("if"); + + let matches_if_negated = match kind { + ConditionKind::Include => false, + ConditionKind::Skip => true, + }; + match value { + None => false, + Some(v) => match v.as_variable() { + Some(directive_var) => conditions.0.iter().any(|(cond_name, cond_is_negated)| { + cond_name == directive_var && *cond_is_negated == matches_if_negated + }), + None => true, + }, + } +} diff --git a/apollo-federation/src/query_plan/display.rs b/apollo-federation/src/query_plan/display.rs new file mode 100644 index 0000000000..da81fd057d --- /dev/null +++ b/apollo-federation/src/query_plan/display.rs @@ -0,0 +1,407 @@ +use std::fmt; + +use apollo_compiler::executable; + +use super::*; +use crate::indented_display::write_indented_lines; +use crate::indented_display::State; + +impl QueryPlan { + fn write_indented(&self, state: &mut State<'_, '_>) -> fmt::Result { + let Self { + node, + statistics: _, + } = self; + state.write("QueryPlan {")?; + if let Some(node) = node { + state.indent()?; + node.write_indented(state)?; + state.dedent()?; + } + state.write("}") + } +} + +impl TopLevelPlanNode { + fn write_indented(&self, state: &mut State<'_, '_>) -> fmt::Result { + match self { + Self::Subscription(node) => node.write_indented(state), + Self::Fetch(node) => node.write_indented(state), + Self::Sequence(node) => node.write_indented(state), + Self::Parallel(node) => node.write_indented(state), + Self::Flatten(node) => node.write_indented(state), + Self::Defer(node) => node.write_indented(state), + Self::Condition(node) => node.write_indented(state), + } + } +} + +impl PlanNode { + fn write_indented(&self, state: &mut State<'_, '_>) -> fmt::Result { + match self { + Self::Fetch(node) => node.write_indented(state), + Self::Sequence(node) => node.write_indented(state), + Self::Parallel(node) => node.write_indented(state), + Self::Flatten(node) => node.write_indented(state), + Self::Defer(node) => node.write_indented(state), + Self::Condition(node) => node.write_indented(state), + } + } +} + +impl SubscriptionNode { + fn write_indented(&self, state: &mut State<'_, '_>) -> fmt::Result { + let Self { primary, rest } = self; + state.write("Subscription {")?; + state.indent()?; + + state.write("Primary: {")?; + primary.write_indented(state)?; + state.write("},")?; + + if let Some(rest) = rest { + state.new_line()?; + state.write("Rest: {")?; + rest.write_indented(state)?; + state.write("},")?; + } + + state.dedent()?; + state.write("},") + } +} + +impl FetchNode { + fn write_indented(&self, state: &mut State<'_, '_>) -> fmt::Result { + let Self { + subgraph_name, + id, + variable_usages: _, + requires, + operation_document, + operation_name: _, + operation_kind: _, + input_rewrites: _, + output_rewrites: _, + } = self; + state.write(format_args!("Fetch(service: {subgraph_name:?}"))?; + if let Some(id) = id { + state.write(format_args!(", id: {id:?}"))?; + } + state.write(") {")?; + state.indent()?; + + if let Some(v) = requires.as_ref() { + if !v.is_empty() { + write_selections(state, v)?; + state.write(" =>")?; + state.new_line()?; + } + } + write_operation(state, operation_document)?; + + state.dedent()?; + state.write("},") + } +} + +impl SequenceNode { + fn write_indented(&self, state: &mut State<'_, '_>) -> fmt::Result { + let Self { nodes } = self; + state.write("Sequence {")?; + + write_indented_lines(state, nodes, |state, node| node.write_indented(state))?; + + state.write("},") + } +} + +impl ParallelNode { + fn write_indented(&self, state: &mut State<'_, '_>) -> fmt::Result { + let Self { nodes } = self; + state.write("Parallel {")?; + + write_indented_lines(state, nodes, |state, node| node.write_indented(state))?; + + state.write("},") + } +} + +impl FlattenNode { + fn write_indented(&self, state: &mut State<'_, '_>) -> fmt::Result { + let Self { path, node } = self; + state.write("Flatten(path: \"")?; + if let Some((first, rest)) = path.split_first() { + state.write(first)?; + for element in rest { + state.write(".")?; + state.write(element)?; + } + } + state.write("\") {")?; + state.indent()?; + + node.write_indented(state)?; + + state.dedent()?; + state.write("},") + } +} + +impl ConditionNode { + fn write_indented(&self, state: &mut State<'_, '_>) -> fmt::Result { + let Self { + condition_variable, + if_clause, + else_clause, + } = self; + match (if_clause, else_clause) { + (Some(if_clause), Some(else_clause)) => { + state.write(format_args!("Condition(if: ${condition_variable}) {{"))?; + state.indent()?; + + state.write("Then {")?; + state.indent()?; + if_clause.write_indented(state)?; + state.dedent()?; + state.write("},")?; + + state.write("Else {")?; + state.indent()?; + else_clause.write_indented(state)?; + state.dedent()?; + state.write("},")?; + + state.dedent()?; + state.write("},") + } + + (Some(if_clause), None) => { + state.write(format_args!("Include(if: ${condition_variable}) {{"))?; + state.indent()?; + + if_clause.write_indented(state)?; + + state.dedent()?; + state.write("},") + } + + (None, Some(else_clause)) => { + state.write(format_args!("Skip(if: ${condition_variable}) {{"))?; + state.indent()?; + + else_clause.write_indented(state)?; + + state.dedent()?; + state.write("},") + } + + // Shouldn’t happen? + (None, None) => state.write("Condition {}"), + } + } +} + +impl DeferNode { + fn write_indented(&self, state: &mut State<'_, '_>) -> fmt::Result { + let Self { primary, deferred } = self; + state.write("Defer {")?; + state.indent()?; + + primary.write_indented(state)?; + if !deferred.is_empty() { + state.write(", [")?; + write_indented_lines(state, deferred, |state, deferred| { + deferred.write_indented(state) + })?; + state.write("]")?; + } + + state.dedent()?; + state.write("},") + } +} + +impl PrimaryDeferBlock { + fn write_indented(&self, state: &mut State<'_, '_>) -> fmt::Result { + let Self { + sub_selection, + node, + } = self; + state.write("Primary {")?; + if sub_selection.is_some() || node.is_some() { + state.indent()?; + + if let Some(sub_selection) = sub_selection { + state.write( + sub_selection + .serialize() + .initial_indent_level(state.indent_level()), + )?; + if node.is_some() { + state.write(":")?; + state.new_line()?; + } + } + if let Some(node) = node { + node.write_indented(state)?; + } + + state.dedent()?; + } + state.write("},") + } +} + +impl DeferredDeferBlock { + fn write_indented(&self, state: &mut State<'_, '_>) -> fmt::Result { + let Self { + depends, + label, + query_path, + sub_selection, + node, + } = self; + state.write("Deferred(depends: [")?; + if let Some((DeferredDependency { id }, rest)) = depends.split_first() { + state.write(id)?; + for DeferredDependency { id } in rest { + state.write(", ")?; + state.write(id)?; + } + } + state.write("], path: \"")?; + if let Some((first, rest)) = query_path.split_first() { + state.write(first)?; + for element in rest { + state.write("/")?; + state.write(element)?; + } + } + state.write("\"")?; + if let Some(label) = label { + state.write(", label: \"")?; + state.write(label)?; + state.write("\"")?; + } + state.write(") {")?; + if sub_selection.is_some() || node.is_some() { + state.indent()?; + + if let Some(sub_selection) = sub_selection { + write_selections(state, &sub_selection.selections)?; + } + if let Some(node) = node { + node.write_indented(state)?; + } + + state.dedent()?; + } + state.write("},") + } +} + +/// When we serialize a query plan, we want to serialize the operation +/// but not show the root level `query` definition or the `_entities` call. +/// This function flattens those nodes to only show their selection sets +fn write_operation( + state: &mut State<'_, '_>, + operation_document: &ExecutableDocument, +) -> fmt::Result { + let operation = operation_document + .get_operation(None) + .expect("expected a single-operation document"); + if operation.operation_type == executable::OperationType::Query { + write_selections(state, &operation.selection_set.selections)? + } else { + state.write( + operation + .serialize() + .initial_indent_level(state.indent_level()), + )? + } + for fragment in operation_document.fragments.values() { + state.new_line()?; + state.write( + fragment + .serialize() + .initial_indent_level(state.indent_level()), + )? + } + Ok(()) +} + +fn write_selections( + state: &mut State<'_, '_>, + mut selections: &[executable::Selection], +) -> fmt::Result { + if let Some(executable::Selection::Field(field)) = selections.first() { + if field.name == "_entities" { + selections = &field.selection_set.selections + } + } + state.write("{")?; + + // Manually indent and write the newline + // to prevent a duplicate indent from `.new_line()` and `.initial_indent_level()`. + state.indent_no_new_line(); + state.write("\n")?; + for sel in selections { + state.write(sel.serialize().initial_indent_level(state.indent_level()))?; + } + state.dedent()?; + + state.write("}") +} + +impl fmt::Display for FetchDataPathElement { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Key(name) => f.write_str(name), + Self::AnyIndex => f.write_str("*"), + Self::TypenameEquals(name) => write!(f, "... on {name}"), + } + } +} + +impl fmt::Display for QueryPathElement { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Field(field) => f.write_str(field.response_key()), + Self::InlineFragment(inline) => { + if let Some(cond) = &inline.type_condition { + write!(f, "... on {cond}") + } else { + Ok(()) + } + } + } + } +} + +macro_rules! impl_display { + ($( $Ty: ty )+) => { + $( + impl fmt::Display for $Ty { + fn fmt(&self, output: &mut fmt::Formatter<'_>) -> fmt::Result { + self.write_indented(&mut State::new(output)) + } + } + )+ + }; +} + +impl_display! { + QueryPlan + TopLevelPlanNode + PlanNode + SubscriptionNode + FetchNode + SequenceNode + ParallelNode + FlattenNode + ConditionNode + DeferNode + PrimaryDeferBlock + DeferredDeferBlock +} diff --git a/apollo-federation/src/query_plan/fetch_dependency_graph.rs b/apollo-federation/src/query_plan/fetch_dependency_graph.rs new file mode 100644 index 0000000000..4710c26ad2 --- /dev/null +++ b/apollo-federation/src/query_plan/fetch_dependency_graph.rs @@ -0,0 +1,2560 @@ +use std::collections::HashSet; +use std::fmt::Write as _; +use std::sync::atomic::AtomicU64; +use std::sync::Arc; +use std::sync::OnceLock; + +use apollo_compiler::ast::Argument; +use apollo_compiler::ast::Directive; +use apollo_compiler::ast::OperationType; +use apollo_compiler::ast::Type; +use apollo_compiler::executable::VariableDefinition; +use apollo_compiler::executable::{self}; +use apollo_compiler::name; +use apollo_compiler::schema::Name; +use apollo_compiler::schema::{self}; +use apollo_compiler::Node; +use apollo_compiler::NodeStr; +use indexmap::IndexMap; +use indexmap::IndexSet; +use petgraph::stable_graph::EdgeIndex; +use petgraph::stable_graph::NodeIndex; +use petgraph::stable_graph::StableDiGraph; +use petgraph::visit::EdgeRef; + +use crate::error::FederationError; +use crate::error::SingleFederationError; +use crate::link::graphql_definition::DeferDirectiveArguments; +use crate::query_graph::graph_path::OpGraphPathContext; +use crate::query_graph::graph_path::OpGraphPathTrigger; +use crate::query_graph::graph_path::OpPath; +use crate::query_graph::graph_path::OpPathElement; +use crate::query_graph::path_tree::OpPathTree; +use crate::query_graph::path_tree::PathTreeChild; +use crate::query_graph::QueryGraph; +use crate::query_graph::QueryGraphEdgeTransition; +use crate::query_plan::conditions::remove_conditions_from_selection_set; +use crate::query_plan::conditions::Conditions; +use crate::query_plan::fetch_dependency_graph_processor::FetchDependencyGraphProcessor; +use crate::query_plan::operation::Field; +use crate::query_plan::operation::FieldData; +use crate::query_plan::operation::InlineFragment; +use crate::query_plan::operation::InlineFragmentData; +use crate::query_plan::operation::Operation; +use crate::query_plan::operation::RebasedFragments; +use crate::query_plan::operation::Selection; +use crate::query_plan::operation::SelectionId; +use crate::query_plan::operation::SelectionMap; +use crate::query_plan::operation::SelectionSet; +use crate::query_plan::operation::TYPENAME_FIELD; +use crate::query_plan::FetchDataPathElement; +use crate::query_plan::FetchDataRewrite; +use crate::query_plan::FetchDataValueSetter; +use crate::query_plan::QueryPlanCost; +use crate::schema::position::CompositeTypeDefinitionPosition; +use crate::schema::position::FieldDefinitionPosition; +use crate::schema::position::ObjectTypeDefinitionPosition; +use crate::schema::position::SchemaRootDefinitionKind; +use crate::schema::ValidFederationSchema; +use crate::subgraph::spec::ANY_SCALAR_NAME; +use crate::subgraph::spec::ENTITIES_QUERY; + +/// Represents the value of a `@defer(label:)` argument. +type DeferRef = NodeStr; + +/// Map of defer labels to nodes of the fetch dependency graph. +type DeferredNodes = multimap::MultiMap>; + +use crate::query_graph::extract_subgraphs_from_supergraph::FEDERATION_REPRESENTATIONS_ARGUMENTS_NAME; +use crate::query_graph::extract_subgraphs_from_supergraph::FEDERATION_REPRESENTATIONS_VAR_NAME; + +/// Represents a subgraph fetch of a query plan. +// PORT_NOTE: The JS codebase called this `FetchGroup`, but this naming didn't make it apparent that +// this was a node in a fetch dependency graph, so we've renamed it accordingly. +// +// The JS codebase additionally has a property named `subgraphAndMergeAtKey` that was used as a +// precomputed map key, but this isn't necessary in Rust since we can use `PartialEq`/`Eq`/`Hash`. +#[derive(Debug, Clone)] +pub(crate) struct FetchDependencyGraphNode { + /// The subgraph this fetch is queried against. + pub(crate) subgraph_name: NodeStr, + /// Which root operation kind the fetch should have. + root_kind: SchemaRootDefinitionKind, + /// The parent type of the fetch's selection set. For fetches against the root, this is the + /// subgraph's root operation type for the corresponding root kind, but for entity fetches this + /// will be the subgraph's entity union type. + parent_type: CompositeTypeDefinitionPosition, + /// The selection set to be fetched from the subgraph, along with memoized conditions. + selection_set: FetchSelectionSet, + /// Whether this fetch uses the federation `_entities` field and correspondingly is against the + /// subgraph's entity union type (sometimes called a "key" fetch). + is_entity_fetch: bool, + /// The inputs to be passed into `_entities` field, if this is an entity fetch. + inputs: Option>, + /// Input rewrites for query plan execution to perform prior to executing the fetch. + input_rewrites: Arc>>, + /// As query plan execution runs, it accumulates fetch data into a response object. This is the + /// path at which to merge in the data for this particular fetch. + merge_at: Option>, + /// The fetch ID generation, if one is necessary (used when handling `@defer`). + /// + /// This can be treated as an Option using `OnceLock::get()`. + id: OnceLock, + /// The label of the `@defer` block this fetch appears in, if any. + defer_ref: Option, + /// The cached computation of this fetch's cost, if it's been done already. + cached_cost: Option, + /// Set in some code paths to indicate that the selection set of the node should not be + /// optimized away even if it "looks" useless. + must_preserve_selection_set: bool, + /// If true, then we skip an expensive computation during `is_useless()`. (This partially + /// caches that computation.) + is_known_useful: bool, +} + +/// Safely generate IDs for fetch dependency nodes without mutable access. +#[derive(Debug)] +struct FetchIdGenerator { + next: AtomicU64, +} +impl FetchIdGenerator { + /// Create an ID generator, starting at the given value. + pub fn new(start_at: u64) -> Self { + Self { + next: AtomicU64::new(start_at), + } + } + + /// Generate a new ID for a fetch dependency node. + pub fn next_id(&self) -> u64 { + self.next.fetch_add(1, std::sync::atomic::Ordering::Relaxed) + } +} + +impl Clone for FetchIdGenerator { + fn clone(&self) -> Self { + Self { + next: AtomicU64::new(self.next.load(std::sync::atomic::Ordering::Relaxed)), + } + } +} + +#[derive(Debug, Clone)] +pub(crate) struct FetchSelectionSet { + /// The selection set to be fetched from the subgraph. + pub(crate) selection_set: Arc, + /// The conditions determining whether the fetch should be executed (which must be recomputed + /// from the selection set when it changes). + pub(crate) conditions: Conditions, +} + +// PORT_NOTE: The JS codebase additionally has a property `onUpdateCallback`. This was only ever +// used to update `isKnownUseful` in `FetchGroup`, and it's easier to handle this there than try +// to pass in a callback in Rust. +#[derive(Debug, Clone)] +pub(crate) struct FetchInputs { + /// The selection sets to be used as input to `_entities`, separated per parent type. + selection_sets_per_parent_type: IndexMap>, + /// The supergraph schema (primarily used for validation of added selection sets). + supergraph_schema: ValidFederationSchema, +} + +/// Represents a dependency between two subgraph fetches, namely that the tail/child depends on the +/// head/parent executing first. +#[derive(Debug, Clone)] +pub(crate) struct FetchDependencyGraphEdge { + /// The operation path of the tail/child _relative_ to the head/parent. This information is + /// maintained in case we want/need to merge nodes into each other. This can roughly be thought + /// of similarly to `merge_at` in the child, but is relative to the start of the parent. It can + /// be `None`, which either means we don't know the relative path, or that the concept of a + /// relative path doesn't make sense in this context. E.g. there is case where a child's + /// `merge_at` can be shorter than its parent's, in which case the `path` (which is essentially + /// `child.merge_at - parent.merge_at`), does not make sense (or rather, it's negative, which we + /// cannot represent). The gist is that `None` for the `path` means that no assumption should be + /// made, and that any merge logic using said path should bail. + path: Option>, +} + +type FetchDependencyGraphPetgraph = + StableDiGraph, Arc>; + +/// A directed acyclic graph (DAG) of fetches (a.k.a. fetch groups) and their dependencies. +/// +/// In the graph, two fetches are connected if one of them (the parent/head) must be performed +/// strictly before the other one (the child/tail). +#[derive(Debug, Clone)] +pub(crate) struct FetchDependencyGraph { + /// The supergraph schema that generated the federated query graph. + supergraph_schema: ValidFederationSchema, + /// The federated query graph that generated the fetches. (This also contains the subgraph + /// schemas.) + federated_query_graph: Arc, + /// The nodes/edges of the fetch dependency graph. Note that this must be a stable graph since + /// we remove nodes/edges during optimizations. + graph: FetchDependencyGraphPetgraph, + /// The root nodes by subgraph name, representing the fetches against root operation types of + /// the subgraphs. + root_nodes_by_subgraph: IndexMap, + /// Tracks metadata about deferred blocks and their dependencies on one another. + pub(crate) defer_tracking: DeferTracking, + /// The initial fetch ID generation (used when handling `@defer`). + starting_id_generation: u64, + /// The current fetch ID generation (used when handling `@defer`). + fetch_id_generation: FetchIdGenerator, + /// Whether this fetch dependency graph has undergone a transitive reduction. + is_reduced: bool, + /// Whether this fetch dependency graph has undergone optimization (e.g. transitive reduction, + /// removing empty/useless fetches, merging fetches with the same subgraph/path). + is_optimized: bool, +} + +// TODO: Write docstrings +#[derive(Debug, Clone)] +pub(crate) struct DeferTracking { + pub(crate) top_level_deferred: IndexSet, + pub(crate) deferred: IndexMap, + pub(crate) primary_selection: Option, +} + +// TODO: Write docstrings +// TODO(@goto-bus-stop): this does not seem like it should be cloned around +#[derive(Debug, Clone)] +pub(crate) struct DeferredInfo { + pub(crate) label: DeferRef, + pub(crate) path: FetchDependencyGraphNodePath, + pub(crate) sub_selection: SelectionSet, + pub(crate) deferred: IndexSet, + pub(crate) dependencies: IndexSet, +} + +// TODO: Write docstrings +#[derive(Debug, Clone, Default)] +pub(crate) struct FetchDependencyGraphNodePath { + pub(crate) full_path: Arc, + path_in_node: Arc, + response_path: Vec, +} + +#[derive(Debug, Clone)] +pub(crate) struct DeferContext { + current_defer_ref: Option, + path_to_defer_parent: Arc, + active_defer_ref: Option, + is_part_of_query: bool, +} + +/// Used in `FetchDependencyGraph` to store, for a given node, information about one of its parent. +/// Namely, this structure stores: +/// 1. the actual parent node index, and +/// 2. the path of the node for which this is a "parent relation" into said parent (`path_in_parent`). This information +/// is maintained for the case where we want/need to merge nodes into each other. One can roughly think of +/// this as similar to a `mergeAt`, but that is relative to the start of `group`. It can be `None`, which +/// either mean we don't know that path or that this simply doesn't make sense (there is case where a child `mergeAt` can +/// be shorter than its parent's, in which case the `path`, which is essentially `child-mergeAt - parent-mergeAt`, does +/// not make sense (or rather, it's negative, which we cannot represent)). Tl;dr, `None` for the `path` means that +/// should make no assumption and bail on any merging that uses said path. +// PORT_NOTE: In JS this uses reference equality, not structural equality, so maybe we should just +// do pointer comparisons? +#[derive(Debug, Clone, PartialEq)] +struct ParentRelation { + parent_node_id: NodeIndex, + path_in_parent: Option>, +} + +/// UnhandledNode is used while processing fetch nodes in dependency order to track nodes for which +/// one of the parents has been processed/handled but which has other parents. +// PORT_NOTE: In JS this was a tuple +#[derive(Debug)] +struct UnhandledNode { + /// The unhandled node. + node: NodeIndex, + /// The parents that still need to be processed before the node can be. + unhandled_parents: Vec, +} + +impl std::fmt::Display for UnhandledNode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{} (missing: [", self.node.index(),)?; + for (i, unhandled) in self.unhandled_parents.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, "{}", unhandled.parent_node_id.index())?; + } + write!(f, "])") + } +} + +/// Used during the processing of fetch nodes in dependency order. +#[derive(Debug)] +struct ProcessingState { + /// Nodes that can be handled (because all their parents/dependencies have been processed before). + // TODO(@goto-bus-stop): Seems like this should be an IndexSet, since every `.push()` first + // checks if the element is unique. + pub next: Vec, + /// Nodes that needs some parents/dependencies to be processed first before they can be themselves. + /// Note that we make sure that this never hold node with no "edges". + pub unhandled: Vec, +} + +impl DeferContext { + fn after_subgraph_jump(&self) -> Self { + Self { + active_defer_ref: self.current_defer_ref.clone(), + // Clone the rest as-is + current_defer_ref: self.current_defer_ref.clone(), + path_to_defer_parent: self.path_to_defer_parent.clone(), + is_part_of_query: self.is_part_of_query, + } + } +} + +impl Default for DeferContext { + fn default() -> Self { + Self { + current_defer_ref: None, + path_to_defer_parent: Default::default(), + active_defer_ref: None, + is_part_of_query: true, + } + } +} + +impl ProcessingState { + pub fn empty() -> Self { + Self { + next: vec![], + unhandled: vec![], + } + } + + pub fn of_ready_nodes(nodes: Vec) -> Self { + Self { + next: nodes, + unhandled: vec![], + } + } + + // PORT_NOTE: `forChildrenOfProcessedNode` is moved into the FetchDependencyGraph + // structure as `create_state_for_children_of_processed_node`, because it needs access to the + // graph. + + pub fn merge_with(self, other: ProcessingState) -> ProcessingState { + let mut next = self.next; + for g in other.next { + if !next.contains(&g) { + next.push(g); + } + } + + let mut unhandled = vec![]; + let mut that_unhandled = other.unhandled; + + fn merge_remains_and_remove_if_found( + node_index: NodeIndex, + mut in_edges: Vec, + other_nodes: &mut Vec, + ) -> Vec { + let Some((other_index, other_node)) = other_nodes + .iter() + .enumerate() + .find(|(_index, other)| other.node == node_index) + else { + return in_edges; + }; + + // The uhandled are the one that are unhandled on both side. + in_edges.retain(|e| !other_node.unhandled_parents.contains(e)); + other_nodes.remove(other_index); + in_edges + } + + for node in self.unhandled { + let new_edges = merge_remains_and_remove_if_found( + node.node, + node.unhandled_parents, + &mut that_unhandled, + ); + if new_edges.is_empty() { + if !next.contains(&node.node) { + next.push(node.node) + } + } else { + unhandled.push(UnhandledNode { + node: node.node, + unhandled_parents: new_edges, + }); + } + } + + // Anything remaining in `thatUnhandled` are nodes that were not in `self` at all. + unhandled.extend(that_unhandled); + + ProcessingState { next, unhandled } + } + + pub fn update_for_processed_nodes(self, processed: &[NodeIndex]) -> ProcessingState { + let mut next = self.next; + let mut unhandled = vec![]; + for UnhandledNode { + node: g, + unhandled_parents: mut edges, + } in self.unhandled + { + // Remove any of the processed nodes from the unhandled edges of that node. + // And if there is no remaining edge, that node can be handled. + edges.retain(|edge| processed.contains(&edge.parent_node_id)); + if edges.is_empty() { + if !next.contains(&g) { + next.push(g); + } + } else { + unhandled.push(UnhandledNode { + node: g, + unhandled_parents: edges, + }); + } + } + ProcessingState { next, unhandled } + } +} + +impl FetchDependencyGraphNodePath { + fn for_new_key_fetch(&self, new_context: Arc) -> Self { + Self { + full_path: self.full_path.clone(), + path_in_node: new_context, + response_path: self.response_path.clone(), + } + } + + fn add( + &self, + element: Arc, + ) -> Result { + Ok(Self { + response_path: self.updated_response_path(&element)?, + full_path: Arc::new(self.full_path.with_pushed(element.clone())), + path_in_node: Arc::new(self.path_in_node.with_pushed(element)), + }) + } + + fn updated_response_path( + &self, + element: &OpPathElement, + ) -> Result, FederationError> { + let mut new_path = self.response_path.clone(); + if let OpPathElement::Field(field) = element { + new_path.push(FetchDataPathElement::Key( + field.data().response_name().into(), + )); + // TODO: is there a simpler we to find a field’s type from `&Field`? + let mut type_ = &field + .data() + .field_position + .get(field.data().schema.schema())? + .ty; + loop { + match type_ { + schema::Type::Named(_) | schema::Type::NonNullNamed(_) => break, + schema::Type::List(inner) | schema::Type::NonNullList(inner) => { + new_path.push(FetchDataPathElement::AnyIndex); + type_ = inner + } + } + } + }; + Ok(new_path) + } +} + +impl FetchDependencyGraph { + pub(crate) fn new( + supergraph_schema: ValidFederationSchema, + federated_query_graph: Arc, + root_type_for_defer: Option, + starting_id_generation: u64, + ) -> Self { + Self { + defer_tracking: DeferTracking::empty(&supergraph_schema, root_type_for_defer), + supergraph_schema, + federated_query_graph, + graph: Default::default(), + root_nodes_by_subgraph: Default::default(), + starting_id_generation, + fetch_id_generation: FetchIdGenerator::new(starting_id_generation), + is_reduced: false, + is_optimized: false, + } + } + + /// Must be called every time the "shape" of the graph is modified + /// to know that the graph may not be minimal/optimized anymore. + fn on_modification(&mut self) { + self.is_reduced = false; + self.is_optimized = false; + } + + pub(crate) fn get_or_create_root_node( + &mut self, + subgraph_name: &NodeStr, + root_kind: SchemaRootDefinitionKind, + parent_type: CompositeTypeDefinitionPosition, + ) -> Result { + if let Some(node) = self.root_nodes_by_subgraph.get(subgraph_name) { + return Ok(*node); + } + let node = self.new_node( + subgraph_name.clone(), + parent_type, + /* has_inputs: */ false, + root_kind, + None, + None, + )?; + self.root_nodes_by_subgraph + .insert(subgraph_name.clone(), node); + Ok(node) + } + + fn new_root_type_node( + &mut self, + subgraph_name: NodeStr, + root_kind: SchemaRootDefinitionKind, + parent_type: &ObjectTypeDefinitionPosition, + merge_at: Option>, + defer_ref: Option, + ) -> Result { + let has_inputs = false; + self.new_node( + subgraph_name, + parent_type.clone().into(), + has_inputs, + root_kind, + merge_at, + defer_ref, + ) + } + + pub(crate) fn new_node( + &mut self, + subgraph_name: NodeStr, + parent_type: CompositeTypeDefinitionPosition, + has_inputs: bool, + root_kind: SchemaRootDefinitionKind, + merge_at: Option>, + defer_ref: Option, + ) -> Result { + let subgraph_schema = self + .federated_query_graph + .schema_by_source(&subgraph_name)? + .clone(); + self.on_modification(); + Ok(self.graph.add_node(Arc::new(FetchDependencyGraphNode { + subgraph_name, + root_kind, + selection_set: FetchSelectionSet::empty(subgraph_schema, parent_type.clone())?, + parent_type, + is_entity_fetch: has_inputs, + inputs: has_inputs + .then(|| Arc::new(FetchInputs::empty(self.supergraph_schema.clone()))), + input_rewrites: Default::default(), + merge_at, + id: OnceLock::new(), + defer_ref, + cached_cost: None, + must_preserve_selection_set: false, + is_known_useful: false, + }))) + } + + pub(crate) fn node_weight( + &self, + node: NodeIndex, + ) -> Result<&Arc, FederationError> { + self.graph + .node_weight(node) + .ok_or_else(|| FederationError::internal("Node unexpectedly missing")) + } + + /// Does not take `&mut self` so that other fields can be mutated while this borrow lasts + fn node_weight_mut( + graph: &mut FetchDependencyGraphPetgraph, + node: NodeIndex, + ) -> Result<&mut FetchDependencyGraphNode, FederationError> { + Ok(Arc::make_mut(graph.node_weight_mut(node).ok_or_else( + || FederationError::internal("Node unexpectedly missing".to_owned()), + )?)) + } + + pub(crate) fn edge_weight( + &self, + edge: EdgeIndex, + ) -> Result<&Arc, FederationError> { + self.graph + .edge_weight(edge) + .ok_or_else(|| FederationError::internal("Edge unexpectedly missing".to_owned())) + } + + /// Does not take `&mut self` so that other fields can be mutated while this borrow lasts + fn edge_weight_mut( + graph: &mut FetchDependencyGraphPetgraph, + edge: EdgeIndex, + ) -> Result<&mut FetchDependencyGraphEdge, FederationError> { + Ok(Arc::make_mut(graph.edge_weight_mut(edge).ok_or_else( + || FederationError::internal("Edge unexpectedly missing"), + )?)) + } + + fn get_or_create_key_node( + &mut self, + subgraph_name: &NodeStr, + merge_at: &[FetchDataPathElement], + type_: &CompositeTypeDefinitionPosition, + parent: ParentRelation, + conditions_nodes: &IndexSet, + defer_ref: Option<&DeferRef>, + ) -> Result { + // Let's look if we can reuse a node we have, that is an existing child of the parent that: + // 1. is for the same subgraph + // 2. has the same merge_at + // 3. is for the same entity type (we don't reuse nodes for different entities just yet, + // as this can create unecessary dependencies that gets in the way of some optimizations; + // the final optimizations in `reduceAndOptimize` will however later merge nodes + // on the same subgraph and mergeAt when possible). + // 4. is not part of our conditions or our conditions ancestors + // (meaning that we annot reuse a node if it fetches something we take as input). + // 5. is part of the same "defer" grouping + // 6. has the same path in parents (here again, we _will_ eventually merge fetches + // for which this is not true later in `reduceAndOptimize`, but for now, + // keeping nodes separated when they have a different path in their parent + // allows to keep that "path in parent" more precisely, + // which is important for some case of @requires). + for existing_id in self.children_of(parent.parent_node_id) { + let existing = self.node_weight(existing_id)?; + if existing.subgraph_name == *subgraph_name + && existing.merge_at.as_deref() == Some(merge_at) + && existing + .selection_set + .selection_set + .selections + .values() + .all(|selection| { + matches!( + selection, + Selection::InlineFragment(fragment) + if fragment.casted_type() == type_ + ) + }) + && !self.is_in_nodes_or_their_ancestors(existing_id, conditions_nodes) + && existing.defer_ref.as_ref() == defer_ref + && self + .parents_relations_of(existing_id) + .find(|rel| rel.parent_node_id == parent.parent_node_id) + .and_then(|rel| rel.path_in_parent) + == parent.path_in_parent + { + return Ok(existing_id); + } + } + let new_node = self.new_key_node(subgraph_name, merge_at.to_vec(), defer_ref.cloned())?; + self.add_parent(new_node, parent); + Ok(new_node) + } + + fn new_key_node( + &mut self, + subgraph_name: &NodeStr, + merge_at: Vec, + defer_ref: Option, + ) -> Result { + let entity_type = self + .federated_query_graph + .schema_by_source(subgraph_name)? + .entity_type()? + .ok_or_else(|| { + FederationError::internal(format!( + "Subgraph `{subgraph_name}` has no entities defined" + )) + })?; + + self.new_node( + subgraph_name.clone(), + entity_type.into(), + /* has_inputs: */ true, + SchemaRootDefinitionKind::Query, + Some(merge_at), + defer_ref, + ) + } + + /// Adds another node as a parent of `child`, + /// meaning that this fetch should happen after the provided one. + fn add_parent(&mut self, child_id: NodeIndex, parent_relation: ParentRelation) { + let ParentRelation { + parent_node_id, + path_in_parent, + } = parent_relation; + if self.graph.contains_edge(parent_node_id, child_id) { + return; + } + assert!( + !self.graph.contains_edge(child_id, parent_node_id), + "Node {parent_node_id:?} is a child of {child_id:?}: \ + adding it as parent would create a cycle" + ); + self.on_modification(); + self.graph.add_edge( + parent_node_id, + child_id, + Arc::new(FetchDependencyGraphEdge { + path: path_in_parent.clone(), + }), + ); + } + + /// Returns true if `needle` is either part of `haystack`, or is one of their ancestors + /// (potentially recursively). + fn is_in_nodes_or_their_ancestors( + &self, + needle: NodeIndex, + haystack: &IndexSet, + ) -> bool { + if haystack.contains(&needle) { + return true; + } + + // No risk of inifite loop as the graph is acyclic: + let mut to_check = haystack.clone(); + while let Some(next) = to_check.pop() { + for parent in self.parents_of(next) { + if parent == needle { + return true; + } + to_check.insert(parent); + } + } + false + } + + fn children_of(&self, node_id: NodeIndex) -> impl Iterator + '_ { + self.graph + .neighbors_directed(node_id, petgraph::Direction::Outgoing) + } + + fn parents_of(&self, node_id: NodeIndex) -> impl Iterator + '_ { + self.graph + .neighbors_directed(node_id, petgraph::Direction::Incoming) + } + + fn parents_relations_of( + &self, + node_id: NodeIndex, + ) -> impl Iterator + '_ { + self.graph + .edges_directed(node_id, petgraph::Direction::Incoming) + .map(|edge| ParentRelation { + parent_node_id: edge.source(), + path_in_parent: edge.weight().path.clone(), + }) + } + + fn type_for_fetch_inputs( + &self, + type_name: &Name, + ) -> Result { + self.supergraph_schema + .get_type(type_name.clone())? + .try_into() + } + + /// Find redundant edges coming out of a node. See `remove_redundant_edges`. + fn collect_redundant_edges(&self, node_index: NodeIndex, acc: &mut HashSet) { + let mut stack = vec![]; + for start_index in self.children_of(node_index) { + stack.extend(self.children_of(start_index)); + while let Some(v) = stack.pop() { + for edge in self.graph.edges_connecting(node_index, v) { + acc.insert(edge.id()); + } + + stack.extend(self.children_of(v)); + } + } + } + + /// Do a transitive reduction for edges coming out of the given node. + /// + /// If any deeply nested child of this node has an edge to any direct child of this node, the + /// direct child is removed, as we know it is also reachable through the deeply nested route. + fn remove_redundant_edges(&mut self, node_index: NodeIndex) { + let mut redundant_edges = HashSet::new(); + self.collect_redundant_edges(node_index, &mut redundant_edges); + + for edge in redundant_edges { + self.graph.remove_edge(edge); + } + } + + /// Do a transitive reduction (https://en.wikipedia.org/wiki/Transitive_reduction) of the graph + /// We keep it simple and do a DFS from each vertex. The complexity is not amazing, but dependency + /// graphs between fetch nodes will almost surely never be huge and query planning performance + /// is not paramount so this is almost surely "good enough". + fn reduce(&mut self) { + if std::mem::replace(&mut self.is_reduced, true) { + return; + } + + // Two phases for mutability reasons: first all redundant edges coming out of all nodes are + // collected and then they are all removed. + let mut redundant_edges = HashSet::new(); + for node_index in self.graph.node_indices() { + self.collect_redundant_edges(node_index, &mut redundant_edges); + } + + for edge in redundant_edges { + self.graph.remove_edge(edge); + } + } + + /// Reduce the graph (see `reduce`) and then do a some additional traversals to optimize for: + /// 1) fetches with no selection: this can happen when we have a require if the only field requested + /// was the one with the require and that forced some dependencies. Those fetch should have + /// no dependents and we can just remove them. + /// 2) fetches that are made in parallel to the same subgraph and the same path, and merge those. + fn reduce_and_optimize(&mut self) { + if std::mem::replace(&mut self.is_optimized, true) { + return; + } + + self.reduce(); + + // TODO Optimize: FED-55 + } + + fn extract_children_and_deferred_dependencies( + &mut self, + node_index: NodeIndex, + ) -> Result<(Vec, DeferredNodes), FederationError> { + let mut children = vec![]; + let mut deferred_nodes = DeferredNodes::new(); + + let mut defer_dependencies = vec![]; + + let node_children = self + .graph + .neighbors_directed(node_index, petgraph::Direction::Outgoing); + let node = self.node_weight(node_index)?; + for child_index in node_children { + let child = self.node_weight(child_index)?; + if node.defer_ref == child.defer_ref { + children.push(child_index); + } else { + let parent_defer_ref = node.defer_ref.as_ref().unwrap(); + let Some(child_defer_ref) = &child.defer_ref else { + panic!("{} has defer_ref `{parent_defer_ref}`, so its child {} cannot have a top-level defer_ref.", + node.display(node_index), + child.display(child_index), + ); + }; + + if !node.selection_set.selection_set.selections.is_empty() { + let id = *node.id.get_or_init(|| self.fetch_id_generation.next_id()); + defer_dependencies.push((child_defer_ref.clone(), format!("{id}").into())); + } + deferred_nodes.insert(child_defer_ref.clone(), child_index); + } + } + + for (defer_ref, dependency) in defer_dependencies { + self.defer_tracking.add_dependency(&defer_ref, dependency); + } + + Ok((children, deferred_nodes)) + } + + fn create_state_for_children_of_processed_node( + &self, + processed_index: NodeIndex, + children: impl IntoIterator, + ) -> ProcessingState { + let mut next = vec![]; + let mut unhandled = vec![]; + for c in children { + let num_parents = self.parents_of(c).count(); + if num_parents == 1 { + // The parent we have processed is the only one parent of that child; we can handle the children + next.push(c) + } else { + let parents = self + .parents_relations_of(c) + .filter(|parent| parent.parent_node_id != processed_index) + .collect(); + unhandled.push(UnhandledNode { + node: c, + unhandled_parents: parents, + }); + } + } + ProcessingState { next, unhandled } + } + + fn process_node( + &mut self, + processor: &mut impl FetchDependencyGraphProcessor, + node_index: NodeIndex, + handled_conditions: Conditions, + ) -> Result<(TProcessed, DeferredNodes, ProcessingState), FederationError> { + let (children, deferred_nodes) = + self.extract_children_and_deferred_dependencies(node_index)?; + + let node = self + .graph + .node_weight_mut(node_index) + .ok_or_else(|| FederationError::internal("Node unexpectedly missing"))?; + let conditions = handled_conditions.update_with(&node.selection_set.conditions); + let new_handled_conditions = conditions.clone().merge(handled_conditions); + + let processed = processor.on_node( + &self.federated_query_graph, + Arc::make_mut(node), + &new_handled_conditions, + )?; + if children.is_empty() { + return Ok(( + processor.on_conditions(&conditions, processed), + deferred_nodes, + ProcessingState::empty(), + )); + } + + let state = self.create_state_for_children_of_processed_node(node_index, children); + if state.next.is_empty() { + Ok(( + processor.on_conditions(&conditions, processed), + deferred_nodes, + state, + )) + } else { + // We process the ready children as if they were parallel roots (they are from `processed` + // in a way), and then just add process at the beginning of the sequence. + let (main_sequence, all_deferred_nodes, new_state) = self.process_root_main_nodes( + processor, + state, + true, + &deferred_nodes, + new_handled_conditions, + )?; + + let reduced_sequence = + processor.reduce_sequence(std::iter::once(processed).chain(main_sequence)); + Ok(( + processor.on_conditions(&conditions, reduced_sequence), + all_deferred_nodes, + new_state, + )) + } + } + + fn process_nodes( + &mut self, + processor: &mut impl FetchDependencyGraphProcessor, + state: ProcessingState, + process_in_parallel: bool, + handled_conditions: Conditions, + ) -> Result<(TProcessed, DeferredNodes, ProcessingState), FederationError> { + let mut processed_nodes = vec![]; + let mut all_deferred_nodes = DeferredNodes::new(); + let mut new_state = ProcessingState { + next: Default::default(), + unhandled: state.unhandled, + }; + for node_index in &state.next { + let (main, deferred_nodes, state_after_node) = + self.process_node(processor, *node_index, handled_conditions.clone())?; + processed_nodes.push(main); + all_deferred_nodes.extend(deferred_nodes); + new_state = new_state.merge_with(state_after_node); + } + + // Note that `new_state` is the merged result of everything after each individual node (anything that was _only_ depending + // on it), but the fact that nodes themselves (`state.next`) have been handled has not necessarily be taking into + // account yet, so we do it below. Also note that this must be done outside of the `for` loop above, because any + // node that dependend on multiple of the input nodes of this function must not be handled _within_ this function + // but rather after it, and this is what ensures it. + let processed = if process_in_parallel { + processor.reduce_parallel(processed_nodes) + } else { + processor.reduce_sequence(processed_nodes) + }; + Ok(( + processed, + all_deferred_nodes, + new_state.update_for_processed_nodes(&state.next), + )) + } + + /// Process the "main" (non-deferred) nodes starting at the provided roots. The deferred nodes are collected + /// by this method but not otherwise processed. + fn process_root_main_nodes( + &mut self, + processor: &mut impl FetchDependencyGraphProcessor, + mut state: ProcessingState, + roots_are_parallel: bool, + initial_deferred_nodes: &DeferredNodes, + handled_conditions: Conditions, + ) -> Result<(Vec, DeferredNodes, ProcessingState), FederationError> { + let mut main_sequence = vec![]; + let mut all_deferred_nodes = initial_deferred_nodes.clone(); + let mut process_in_parallel = roots_are_parallel; + while !state.next.is_empty() { + let (processed, deferred_nodes, new_state) = self.process_nodes( + processor, + state, + process_in_parallel, + handled_conditions.clone(), + )?; + // After the root nodes, handled on the first iteration, we can process everything in parallel. + process_in_parallel = true; + main_sequence.push(processed); + state = new_state; + all_deferred_nodes.extend(deferred_nodes); + } + + Ok((main_sequence, all_deferred_nodes, state)) + } + + fn process_root_nodes( + &mut self, + processor: &mut impl FetchDependencyGraphProcessor, + root_nodes: Vec, + roots_are_parallel: bool, + current_defer_ref: Option<&str>, + other_defer_nodes: Option<&DeferredNodes>, + handled_conditions: Conditions, + ) -> Result<(Vec, Vec), FederationError> { + let (main_sequence, deferred_nodes, new_state) = self.process_root_main_nodes( + processor, + ProcessingState::of_ready_nodes(root_nodes), + roots_are_parallel, + &Default::default(), + handled_conditions.clone(), + )?; + assert!( + new_state.next.is_empty(), + "Should not have left some ready nodes, but got {:?}", + new_state.next + ); + assert!( + new_state.unhandled.is_empty(), + "Root nodes should have no remaining nodes unhandled, but got: [{}]", + new_state + .unhandled + .iter() + .map(|unhandled| unhandled.to_string()) + .collect::>() + .join(", "), + ); + let mut all_deferred_nodes = other_defer_nodes.cloned().unwrap_or_default(); + all_deferred_nodes.extend(deferred_nodes); + + // We're going to handle all `@defer`s at our "current" level (eg. at the top level, that's all the non-nested @defer), + // and the "starting" node for those defers, if any, are in `all_deferred_nodes`. However, `all_deferred_nodes` + // can actually contain defer nodes that are for "deeper" levels of @defer-nesting, and that is because + // sometimes the key we need to resume a nested @defer is the same as for the current @defer (or put another way, + // a @defer B may be nested inside @defer A "in the query", but be such that we don't need anything fetched within + // the deferred part of A to start the deferred part of B). + // Long story short, we first collect the nodes from `all_deferred_nodes` that are _not_ in our current level, if + // any, and pass those to the recursive call below so they can be use a their proper level of nesting. + let defers_in_current = self.defer_tracking.defers_in_parent(current_defer_ref); + let handled_defers_in_current = defers_in_current + .iter() + .map(|info| info.label.clone()) + .collect::>(); + let unhandled_defer_nodes = all_deferred_nodes + .keys() + .filter(|label| !handled_defers_in_current.contains(*label)) + .map(|label| { + ( + label.clone(), + all_deferred_nodes.get_vec(label).cloned().unwrap(), + ) + }) + .collect::(); + let unhandled_defer_node = if unhandled_defer_nodes.is_empty() { + None + } else { + Some(unhandled_defer_nodes) + }; + + // We now iterate on every @defer of said "current level". Note in particular that we may not be able to truly defer + // anything for some of those @defer due the limitations of what can be done at the query planner level. However, we + // still create `DeferNode` and `DeferredNode` in those case so that the execution can at least defer the sending of + // the response back (future handling of defer-passthrough will also piggy-back on this). + let mut all_deferred: Vec = vec![]; + // TODO(@goto-bus-stop): this clone looks expensive and could be avoided with a refactor + // See also PORT_NOTE in `.defers_in_parent()`. + let defers_in_current = defers_in_current.into_iter().cloned().collect::>(); + for defer in defers_in_current { + let nodes = all_deferred_nodes + .get_vec(&defer.label) + .cloned() + .unwrap_or_default(); + let (main_sequence_of_defer, deferred_of_defer) = self.process_root_nodes( + processor, + nodes, + true, + Some(&defer.label), + unhandled_defer_node.as_ref(), + handled_conditions.clone(), + )?; + let main_reduced = processor.reduce_sequence(main_sequence_of_defer); + let processed = if deferred_of_defer.is_empty() { + main_reduced + } else { + processor.reduce_defer(main_reduced, &defer.sub_selection, deferred_of_defer)? + }; + all_deferred.push(processor.reduce_deferred(&defer, processed)?); + } + Ok((main_sequence, all_deferred)) + } + + /// Processes the "plan" represented by this query graph using the provided `processor`. + /// + /// Returns a main part and a (potentially empty) deferred part. + pub(crate) fn process( + &mut self, + mut processor: impl FetchDependencyGraphProcessor, + root_kind: SchemaRootDefinitionKind, + ) -> Result<(TProcessed, Vec), FederationError> { + self.reduce_and_optimize(); + + let (main_sequence, deferred) = self.process_root_nodes( + &mut processor, + self.root_nodes_by_subgraph.values().cloned().collect(), + root_kind == SchemaRootDefinitionKind::Query, + None, + None, + Conditions::Boolean(true), + )?; + + // Note that the return of `process_root_nodes` should always be reduced as a sequence, regardless of `root_kind`. + // For queries, it just happens in that the majority of cases, `main_sequence` will be an array of a single element + // and that single element will be a parallel node of the actual roots. But there is some special cases where some + // while the roots are started in parallel, the overall plan shape is something like: + // Root1 \ + // -> Other + // Root2 / + // And so it is a sequence, even if the roots will be queried in parallel. + Ok((processor.reduce_sequence(main_sequence), deferred)) + } +} + +impl std::fmt::Display for FetchDependencyGraph { + /// Displays the relationship between subgraph fetches. + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt_node( + g: &FetchDependencyGraph, + node_id: NodeIndex, + f: &mut std::fmt::Formatter<'_>, + indent: usize, + ) -> std::fmt::Result { + let Ok(node) = g.node_weight(node_id) else { + return Ok(()); + }; + for _ in 0..indent { + write!(f, " ")?; + } + write!(f, "{} <- ", node.display(node_id))?; + for (i, child_id) in g.children_of(node_id).enumerate() { + if i > 0 { + f.write_str(", ")?; + } + + let Ok(child) = g.node_weight(child_id) else { + continue; + }; + write!(f, "{}", child.subgraph_name)?; + } + + if g.children_of(node_id).next().is_some() { + f.write_char('\n')?; + } + + for child_id in g.children_of(node_id) { + fmt_node(g, child_id, f, indent + 1)?; + f.write_char('\n')?; + } + Ok(()) + } + + for (i, &node_id) in self.root_nodes_by_subgraph.values().enumerate() { + if i > 0 { + f.write_char('\n')?; + } + fmt_node(self, node_id, f, 0)?; + } + Ok(()) + } +} + +impl FetchDependencyGraphNode { + pub(crate) fn selection_set_mut(&mut self) -> &mut FetchSelectionSet { + self.cached_cost = None; + &mut self.selection_set + } + + fn add_inputs( + &mut self, + supergraph_schema: &ValidFederationSchema, + selection: &SelectionSet, + rewrites: impl IntoIterator>, + ) -> Result<(), FederationError> { + let inputs = self.inputs.get_or_insert_with(|| { + Arc::new(FetchInputs { + selection_sets_per_parent_type: Default::default(), + supergraph_schema: supergraph_schema.clone(), + }) + }); + Arc::make_mut(inputs).add(selection)?; + self.on_inputs_updated(); + Arc::make_mut(&mut self.input_rewrites).extend(rewrites); + Ok(()) + } + + // PORT_NOTE: This corresponds to the `GroupInputs.onUpdateCallback` in the JS codebase. + // The callback is an optional value that is set only if the `inputs` is non-null + // in the `FetchGroup` constructor. + // In Rust version, the `self.inputs` is checked every time the `inputs` is updated, + // assuming `self.inputs` won't be changed from None to Some in the middle of its + // lifetime. + fn on_inputs_updated(&mut self) { + if self.inputs.is_some() { + // (Original comment from the JS codebase with a minor adjustment for Rust version): + // We're trying to avoid the full recomputation of `is_useless` when we're already + // shown that the node is known useful (if it is shown useless, the node is removed, + // so we're not caching that result but it's ok). And `is_useless` basically checks if + // `inputs.contains(selection)`, so if a group is shown useful, it means that there + // is some selections not in the inputs, but as long as we add to selections (and we + // never remove from selections), then this won't change. Only changing inputs may + // require some recomputation. + self.is_known_useful = false; + } + } + + pub(crate) fn cost(&mut self) -> Result { + if self.cached_cost.is_none() { + self.cached_cost = Some(self.selection_set.selection_set.cost(1)?) + } + Ok(self.cached_cost.unwrap()) + } + + pub(crate) fn to_plan_node( + &self, + query_graph: &QueryGraph, + handled_conditions: &Conditions, + variable_definitions: &[Node], + fragments: Option<&mut RebasedFragments>, + operation_name: Option, + ) -> Result, FederationError> { + if self.selection_set.selection_set.selections.is_empty() { + return Ok(None); + } + let (selection, output_rewrites) = + self.finalize_selection(variable_definitions, handled_conditions, &fragments)?; + let input_nodes = self + .inputs + .as_ref() + .map(|inputs| { + inputs.to_selection_set_nodes( + variable_definitions, + handled_conditions, + &self.parent_type, + ) + }) + .transpose()?; + let subgraph_schema = query_graph.schema_by_source(&self.subgraph_name)?; + let variable_usages = selection.used_variables()?; + let mut operation = if self.is_entity_fetch { + operation_for_entities_fetch( + subgraph_schema, + selection, + variable_definitions, + &operation_name, + )? + } else { + operation_for_query_fetch( + subgraph_schema, + self.root_kind, + selection, + variable_definitions, + &operation_name, + )? + }; + let fragments = fragments + .map(|rebased| rebased.for_subgraph(self.subgraph_name.clone(), subgraph_schema)); + operation.optimize(fragments, Default::default()); + let operation_document = operation.try_into()?; + + let node = super::PlanNode::Fetch(Box::new(super::FetchNode { + subgraph_name: self.subgraph_name.clone(), + id: self.id.get().copied(), + variable_usages, + requires: input_nodes + .as_ref() + .map(executable::SelectionSet::try_from) + .transpose()? + .map(|selection_set| selection_set.selections), + operation_document, + operation_name, + operation_kind: self.root_kind.into(), + input_rewrites: self.input_rewrites.clone(), + output_rewrites, + })); + + Ok(Some(if let Some(path) = self.merge_at.clone() { + super::PlanNode::Flatten(super::FlattenNode { + path, + node: Box::new(node), + }) + } else { + node + })) + } + + fn finalize_selection( + &self, + variable_definitions: &[Node], + handled_conditions: &Conditions, + fragments: &Option<&mut RebasedFragments>, + ) -> Result<(SelectionSet, Vec>), FederationError> { + // Finalizing the selection involves the following: + // 1. removing any @include/@skip that are not necessary + // because they are already handled earlier in the query plan + // by some `ConditionNode`. + // 2. adding __typename to all abstract types. + // This is because any follow-up fetch may need + // to select some of the entities fetched by this node, + // and so we need to have the __typename of those. + // 3. checking if some selection violates + // `https://spec.graphql.org/draft/#FieldsInSetCanMerge()`: + // while the original query we plan for will never violate this, + // because the planner adds some additional fields to the query + // (due to @key and @requires) and because type-explosion changes the query, + // we could have violation of this. + // If that is the case, we introduce aliases to the selection to make it valid, + // and then generate a rewrite on the output of the fetch + // so that data aliased this way is rewritten back to the original/proper response name. + let selection_without_conditions = remove_conditions_from_selection_set( + &self.selection_set.selection_set, + handled_conditions, + )?; + let selection_with_typenames = + selection_without_conditions.add_typename_field_for_abstract_types(None, fragments)?; + + let (updated_selection, output_rewrites) = + selection_with_typenames.add_aliases_for_non_merging_fields()?; + + updated_selection.validate(variable_definitions)?; + Ok((updated_selection, output_rewrites)) + } + + /// Return a concise display for this node. The node index in the graph + /// must be passed in externally. + fn display(&self, index: NodeIndex) -> impl std::fmt::Display + '_ { + use std::fmt; + use std::fmt::Display; + use std::fmt::Formatter; + + struct DisplayList<'a, T: Display>(&'a [T]); + impl Display for DisplayList<'_, T> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let mut iter = self.0.iter(); + if let Some(x) = iter.next() { + write!(f, "{x}")?; + } + for x in iter { + write!(f, "::{x}")?; + } + Ok(()) + } + } + + struct FetchDependencyNodeDisplay<'a> { + node: &'a FetchDependencyGraphNode, + index: NodeIndex, + } + + impl Display for FetchDependencyNodeDisplay<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "[{}]", self.index.index())?; + if self.node.defer_ref.is_some() { + write!(f, "(deferred)")?; + } + if let Some(&id) = self.node.id.get() { + write!(f, "{{id: {id}}}")?; + } + + write!(f, " {}", self.node.subgraph_name)?; + + match (self.node.merge_at.as_deref(), self.node.inputs.as_deref()) { + (Some(merge_at), Some(inputs)) => { + write!( + f, + // @(path::to::*::field)[{input1,input2} => { id }] + "@({})[{} => {}]", + DisplayList(merge_at), + inputs, + self.node.selection_set.selection_set + )?; + } + (Some(merge_at), None) => { + write!( + f, + // @(path::to::*::field)[{} => { id }] + "@({})[{{}} => {}]", + DisplayList(merge_at), + self.node.selection_set.selection_set + )?; + } + (None, _) => { + // [{ id }] + write!(f, "[{}]", self.node.selection_set.selection_set)?; + } + } + + Ok(()) + } + } + + FetchDependencyNodeDisplay { node: self, index } + } +} + +fn operation_for_entities_fetch( + subgraph_schema: &ValidFederationSchema, + selection_set: SelectionSet, + all_variable_definitions: &[Node], + operation_name: &Option, +) -> Result { + let mut variable_definitions: Vec> = + Vec::with_capacity(all_variable_definitions.len() + 1); + variable_definitions.push(representations_variable_definition(subgraph_schema)?); + let mut used_variables = HashSet::new(); + selection_set.collect_variables(&mut used_variables)?; + variable_definitions.extend( + all_variable_definitions + .iter() + .filter(|definition| used_variables.contains(&definition.name)) + .cloned(), + ); + + let query_type_name = subgraph_schema.schema().root_operation(OperationType::Query).ok_or_else(|| + FederationError::SingleFederationError(SingleFederationError::InvalidGraphQL { + message: "Subgraphs should always have a query root (they should at least provides _entities)".to_string() + }))?; + + let query_type = match subgraph_schema.get_type(query_type_name.clone())? { + crate::schema::position::TypeDefinitionPosition::Object(o) => o, + _ => { + return Err(FederationError::SingleFederationError( + SingleFederationError::InvalidGraphQL { + message: "the root query type must be an object".to_string(), + }, + )) + } + }; + + if !query_type + .get(subgraph_schema.schema())? + .fields + .contains_key(&ENTITIES_QUERY) + { + return Err(FederationError::SingleFederationError( + SingleFederationError::InvalidGraphQL { + message: "Subgraphs should always have the _entities field".to_string(), + }, + )); + } + + let entities = FieldDefinitionPosition::Object(query_type.field(ENTITIES_QUERY.clone())); + + let entities_call = Selection::from_element( + OpPathElement::Field(Field::new(FieldData { + schema: subgraph_schema.clone(), + field_position: entities, + alias: None, + arguments: Arc::new(vec![executable::Argument { + name: FEDERATION_REPRESENTATIONS_ARGUMENTS_NAME, + value: executable::Value::Variable(FEDERATION_REPRESENTATIONS_VAR_NAME).into(), + } + .into()]), + directives: Default::default(), + sibling_typename: None, + })), + Some(selection_set), + )?; + + let type_position: CompositeTypeDefinitionPosition = subgraph_schema + .get_type(query_type_name.clone())? + .try_into()?; + + let mut map = SelectionMap::new(); + map.insert(entities_call); + + let selection_set = SelectionSet { + schema: subgraph_schema.clone(), + type_position, + selections: Arc::new(map), + }; + + Ok(Operation { + schema: subgraph_schema.clone(), + root_kind: SchemaRootDefinitionKind::Query, + name: operation_name.clone().map(|n| n.try_into()).transpose()?, + variables: Arc::new(variable_definitions), + directives: Default::default(), + selection_set, + named_fragments: Default::default(), + }) +} + +fn operation_for_query_fetch( + subgraph_schema: &ValidFederationSchema, + root_kind: SchemaRootDefinitionKind, + selection_set: SelectionSet, + variable_definitions: &[Node], + operation_name: &Option, +) -> Result { + let mut used_variables = HashSet::new(); + selection_set.collect_variables(&mut used_variables)?; + let variable_definitions = variable_definitions + .iter() + .filter(|definition| used_variables.contains(&definition.name)) + .cloned() + .collect(); + + Ok(Operation { + schema: subgraph_schema.clone(), + root_kind, + name: operation_name.clone().map(|n| n.try_into()).transpose()?, + variables: Arc::new(variable_definitions), + directives: Default::default(), + selection_set, + named_fragments: Default::default(), + }) +} + +fn representations_variable_definition( + schema: &ValidFederationSchema, +) -> Result, FederationError> { + let _metadata = schema + .metadata() + .ok_or_else(|| FederationError::internal("Expected schema to be a federation subgraph"))?; + + let any_name = schema.federation_type_name_in_schema(ANY_SCALAR_NAME)?; + + Ok(VariableDefinition { + name: FEDERATION_REPRESENTATIONS_VAR_NAME, + ty: Type::Named(any_name).non_null().list().non_null().into(), + default_value: None, + directives: Default::default(), + } + .into()) +} + +impl SelectionSet { + pub(crate) fn cost(&self, depth: QueryPlanCost) -> Result { + // The cost is essentially the number of elements in the selection, + // but we make deep element cost a tiny bit more, + // mostly to make things a tad more deterministic + // (typically, if we have an interface with a single implementation, + // then we can have a choice between a query plan that type-explode a field of the interface + // and one that doesn't, and both will be almost identical, + // except that the type-exploded field will be a different depth; + // by favoring lesser depth in that case, we favor not type-exploding). + self.selections.values().try_fold(0, |sum, selection| { + let subselections = match selection { + Selection::Field(field) => field.selection_set.as_ref(), + Selection::InlineFragment(inline) => Some(&inline.selection_set), + Selection::FragmentSpread(_) => { + return Err(FederationError::internal( + "unexpected fragment spread in FetchDependencyGraphNode", + )) + } + }; + let subselections_cost = if let Some(selection_set) = subselections { + selection_set.cost(depth + 1)? + } else { + 0 + }; + Ok(sum + depth + subselections_cost) + }) + } +} + +impl FetchSelectionSet { + pub(crate) fn empty( + schema: ValidFederationSchema, + type_position: CompositeTypeDefinitionPosition, + ) -> Result { + let selection_set = Arc::new(SelectionSet::empty(schema, type_position)); + let conditions = selection_set.conditions()?; + Ok(Self { + conditions, + selection_set, + }) + } + + fn add_at_path( + &mut self, + path_in_node: &OpPath, + selection_set: Option<&Arc>, + ) -> Result<(), FederationError> { + Arc::make_mut(&mut self.selection_set).add_at_path(path_in_node, selection_set)?; + // TODO: when calling this multiple times, maybe only re-compute conditions at the end? + // Or make it lazily-initialized and computed on demand? + self.conditions = self.selection_set.conditions()?; + Ok(()) + } +} + +impl FetchInputs { + pub(crate) fn empty(supergraph_schema: ValidFederationSchema) -> Self { + Self { + selection_sets_per_parent_type: Default::default(), + supergraph_schema, + } + } + + fn add(&mut self, selection: &SelectionSet) -> Result<(), FederationError> { + assert_eq!( + selection.schema, self.supergraph_schema, + "Inputs selections must be based on the supergraph schema" + ); + let type_selections = self + .selection_sets_per_parent_type + .entry(selection.type_position.clone()) + .or_insert_with(|| { + Arc::new(SelectionSet::empty( + selection.schema.clone(), + selection.type_position.clone(), + )) + }); + Arc::make_mut(type_selections).merge_into(std::iter::once(selection)) + // PORT_NOTE: `onUpdateCallback` call is moved to `FetchDependencyGraphNode::on_inputs_updated`. + } + + fn add_all(&mut self, other: &Self) -> Result<(), FederationError> { + for selections in other.selection_sets_per_parent_type.values() { + self.add(selections)?; + } + Ok(()) + } + + fn to_selection_set_nodes( + &self, + variable_definitions: &[Node], + handled_conditions: &Conditions, + type_position: &CompositeTypeDefinitionPosition, + ) -> Result { + let mut selections = SelectionMap::new(); + for selection_set in self.selection_sets_per_parent_type.values() { + let selection_set = + remove_conditions_from_selection_set(selection_set, handled_conditions)?; + // Making sure we're not generating something invalid. + selection_set.validate(variable_definitions)?; + selections.extend_ref(&selection_set.selections) + } + Ok(SelectionSet { + schema: self.supergraph_schema.clone(), + type_position: type_position.clone(), + selections: Arc::new(selections), + }) + } +} + +impl std::fmt::Display for FetchInputs { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.selection_sets_per_parent_type.len() { + 0 => f.write_str("{}"), + 1 => write!( + f, + "{}", + // We can safely unwrap because we know the len >= 1. + self.selection_sets_per_parent_type.values().next().unwrap() + ), + 2.. => { + write!(f, "[")?; + let mut iter = self.selection_sets_per_parent_type.values(); + // We can safely unwrap because we know the len >= 1. + write!(f, "{}", iter.next().unwrap())?; + for x in iter { + write!(f, ",{}", x)?; + } + write!(f, "]") + } + } + } +} + +impl DeferTracking { + fn empty( + schema: &ValidFederationSchema, + root_type_for_defer: Option, + ) -> Self { + Self { + top_level_deferred: Default::default(), + deferred: Default::default(), + primary_selection: root_type_for_defer + .map(|type_position| SelectionSet::empty(schema.clone(), type_position)), + } + } + + fn register_defer( + &mut self, + defer_context: &DeferContext, + defer_args: &DeferDirectiveArguments, + path: FetchDependencyGraphNodePath, + parent_type: CompositeTypeDefinitionPosition, + ) -> Result<(), FederationError> { + // Having the primary selection undefined means that @defer handling is actually disabled, so there's no need to track anything. + let Some(primary_selection) = self.primary_selection.as_mut() else { + return Ok(()); + }; + + let label = defer_args + .label() + .expect("All @defer should have been labeled at this point"); + let _deferred_block = self.deferred.entry(label.clone()).or_insert_with(|| { + DeferredInfo::empty( + primary_selection.schema.clone(), + label.clone(), + path, + parent_type.clone(), + ) + }); + + if let Some(parent_ref) = &defer_context.current_defer_ref { + let Some(parent_info) = self.deferred.get_mut(parent_ref) else { + panic!("Cannot find info for parent {parent_ref} or {label}"); + }; + + parent_info.deferred.insert(label.clone()); + parent_info + .sub_selection + .add_at_path(&defer_context.path_to_defer_parent, None) + } else { + self.top_level_deferred.insert(label.clone()); + primary_selection.add_at_path(&defer_context.path_to_defer_parent, None) + } + } + + fn update_subselection( + &mut self, + defer_context: &DeferContext, + selection_set: Option<&Arc>, + ) -> Result<(), FederationError> { + if !defer_context.is_part_of_query { + return Ok(()); + } + let Some(primary_selection) = &mut self.primary_selection else { + return Ok(()); + }; + if let Some(parent_ref) = &defer_context.current_defer_ref { + self.deferred[parent_ref] + .sub_selection + .add_at_path(&defer_context.path_to_defer_parent, selection_set) + } else { + primary_selection.add_at_path(&defer_context.path_to_defer_parent, selection_set) + } + } + + fn add_dependency(&mut self, label: &str, id_dependency: DeferRef) { + let info = self + .deferred + .get_mut(label) + .expect("Cannot find info for label"); + info.dependencies.insert(id_dependency); + } + + // PORT_NOTE: this probably should just return labels and not the whole DeferredInfo + // to make it a bit easier to work with, since at the usage site, the return value + // is iterated over while also mutating the fetch dependency graph, which is mutually exclusive + // with holding a reference to a DeferredInfo. For now we just clone the return value when + // necessary. + fn defers_in_parent<'s>(&'s self, parent_ref: Option<&str>) -> Vec<&'s DeferredInfo> { + let labels = match parent_ref { + Some(parent_ref) => { + let Some(info) = self.deferred.get(parent_ref) else { + return vec![]; + }; + &info.deferred + } + None => &self.top_level_deferred, + }; + + labels + .iter() + .map(|label| { + self.deferred + .get(label) + .expect("referenced defer label without existing info") + }) + .collect() + } +} + +impl DeferredInfo { + fn empty( + schema: ValidFederationSchema, + label: DeferRef, + path: FetchDependencyGraphNodePath, + parent_type: CompositeTypeDefinitionPosition, + ) -> Self { + Self { + label, + path, + sub_selection: SelectionSet::empty(schema, parent_type), + deferred: Default::default(), + dependencies: Default::default(), + } + } +} + +struct ComputeNodesStackItem<'a> { + tree: &'a OpPathTree, + node_id: NodeIndex, + node_path: FetchDependencyGraphNodePath, + context: &'a OpGraphPathContext, + defer_context: DeferContext, +} + +pub(crate) fn compute_nodes_for_tree( + dependency_graph: &mut FetchDependencyGraph, + initial_tree: &OpPathTree, + initial_node_id: NodeIndex, + initial_node_path: FetchDependencyGraphNodePath, + initial_defer_context: DeferContext, + initial_conditions: &OpGraphPathContext, +) -> Result, FederationError> { + let mut stack = vec![ComputeNodesStackItem { + tree: initial_tree, + node_id: initial_node_id, + node_path: initial_node_path, + context: initial_conditions, + defer_context: initial_defer_context, + }]; + let mut created_nodes = IndexSet::new(); + while let Some(stack_item) = stack.pop() { + let node = + FetchDependencyGraph::node_weight_mut(&mut dependency_graph.graph, stack_item.node_id)?; + for selection_set in &stack_item.tree.local_selection_sets { + node.selection_set_mut() + .add_at_path(&stack_item.node_path.path_in_node, Some(selection_set))?; + dependency_graph + .defer_tracking + .update_subselection(&stack_item.defer_context, Some(selection_set))?; + } + if stack_item.tree.is_leaf() { + node.selection_set_mut() + .add_at_path(&stack_item.node_path.path_in_node, None)?; + dependency_graph + .defer_tracking + .update_subselection(&stack_item.defer_context, None)?; + continue; + } + // We want to preserve the order of the elements in the child, + // but the stack will reverse everything, + // so we iterate in reverse order to counter-balance it. + for child in stack_item.tree.childs.iter().rev() { + match &*child.trigger { + OpGraphPathTrigger::Context(new_context) => { + // The only 3 cases where we can take edge not "driven" by an operation is either: + // * when we resolve a key + // * resolve a query (switch subgraphs because the query root type is the type of a field) + // * or at the root of subgraph graph. + // The latter case has already be handled the beginning of + // `QueryPlanningTraversal::updated_dependency_graph` so only the 2 former remains. + let Some(edge_id) = child.edge else { + return Err(FederationError::internal(format!( + "Unexpected 'null' edge with no trigger at {:?}", + stack_item.node_path + ))); + }; + let edge = stack_item.tree.graph.edge_weight(edge_id)?; + match edge.transition { + QueryGraphEdgeTransition::KeyResolution => { + stack.push(compute_nodes_for_key_resolution( + dependency_graph, + &stack_item, + child, + edge_id, + new_context, + &mut created_nodes, + )?); + } + QueryGraphEdgeTransition::RootTypeResolution { root_kind } => { + stack.push(compute_nodes_for_root_type_resolution( + dependency_graph, + &stack_item, + child, + edge_id, + edge, + root_kind, + new_context, + )?); + } + _ => { + return Err(FederationError::internal(format!( + "Unexpected non-collecting edge {edge}" + ))) + } + } + } + OpGraphPathTrigger::OpPathElement(operation) => { + stack.push(compute_nodes_for_op_path_element( + dependency_graph, + &stack_item, + child, + operation, + &mut created_nodes, + )?); + } + } + } + } + Ok(created_nodes) +} + +fn compute_nodes_for_key_resolution<'a>( + dependency_graph: &mut FetchDependencyGraph, + stack_item: &ComputeNodesStackItem<'a>, + child: &'a PathTreeChild>, + edge_id: EdgeIndex, + new_context: &'a OpGraphPathContext, + created_nodes: &mut IndexSet, +) -> Result, FederationError> { + let edge = stack_item.tree.graph.edge_weight(edge_id)?; + let Some(conditions) = &child.conditions else { + return Err(FederationError::internal(format!( + "Key edge {edge:?} should have some conditions paths", + ))); + }; + // First, we need to ensure we fetch the conditions from the current node. + let conditions_nodes = compute_nodes_for_tree( + dependency_graph, + conditions, + stack_item.node_id, + stack_item.node_path.clone(), + stack_item.defer_context.clone(), + &Default::default(), + )?; + created_nodes.extend(conditions_nodes.iter().copied()); + // Then we can "take the edge", creating a new node. + // That node depends on the condition ones. + let (source_id, dest_id) = stack_item.tree.graph.edge_endpoints(edge_id)?; + let source = stack_item.tree.graph.node_weight(source_id)?; + let dest = stack_item.tree.graph.node_weight(dest_id)?; + // We shouldn't have a key on a non-composite type + let source_type: CompositeTypeDefinitionPosition = source.type_.clone().try_into()?; + let dest_type: CompositeTypeDefinitionPosition = dest.type_.clone().try_into()?; + let path_in_parent = &stack_item.node_path.path_in_node; + let updated_defer_context = stack_item.defer_context.after_subgraph_jump(); + // Note that we use the name of `dest_type` for the inputs parent type, which can seem strange, + // but the reason is that we have 2 kind of cases: + // - either source_type == dest_type, which is the case for an object entity key, + // or for a key from an @interfaceObject to an interface key. + // - or source_type !== dest_type, + // and that means the source is an implementation type X of some interface I, + // and dest_type is an @interfaceObject corresponding to I. + // But in that case, using I as base for the inputs is a bit more flexible + // as it ensure that if the query uses multiple such key for multiple implementations + // (so, key from X to I, and then Y to I), then the same fetch is properly reused. + // Note that it is ok to do so since + // 1) inputs are based on the supergraph schema, so I is going to exist there and + // 2) we wrap the input selection properly against `source_type` below anyway. + let new_node_id = dependency_graph.get_or_create_key_node( + &dest.source, + &stack_item.node_path.response_path, + &dest_type, + ParentRelation { + parent_node_id: stack_item.node_id, + path_in_parent: Some(Arc::clone(path_in_parent)), + }, + &conditions_nodes, + updated_defer_context.active_defer_ref.as_ref(), + )?; + created_nodes.insert(new_node_id); + for condition_node in conditions_nodes { + // If `condition_node` parent is `node_id`, + // that is the same as `new_node_id` current parent, + // then we can infer the path of `new_node_id` into that condition node + // by looking at the paths of each to their common parent. + // But otherwise, we cannot have a proper "path in parent". + let mut path = None; + let mut iter = dependency_graph.parents_relations_of(condition_node); + if let (Some(condition_node_parent), None) = (iter.next(), iter.next()) { + // There is exactly one parent + if condition_node_parent.parent_node_id == stack_item.node_id { + if let Some(condition_path) = condition_node_parent.path_in_parent { + path = condition_path.strip_prefix(path_in_parent).map(Arc::new) + } + } + } + drop(iter); + dependency_graph.add_parent( + new_node_id, + ParentRelation { + parent_node_id: condition_node, + path_in_parent: path, + }, + ) + } + // Note that inputs must be based on the supergraph schema, not any particular subgraph, + // since sometimes key conditions are fetched from multiple subgraphs + // (and so no one subgraph has a type definition with all the proper fields, + // only the supergraph does). + let input_type = dependency_graph.type_for_fetch_inputs(source_type.type_name())?; + let mut input_selections = SelectionSet::for_composite_type( + dependency_graph.supergraph_schema.clone(), + input_type.clone(), + ); + let Some(edge_conditions) = &edge.conditions else { + // PORT_NOTE: TypeScript `computeGroupsForTree()` has a non-null assertion here + return Err(FederationError::internal( + "missing expected edge conditions", + )); + }; + let edge_conditions = edge_conditions.rebase_on( + &input_type, + // Conditions do not use named fragments + &Default::default(), + &dependency_graph.supergraph_schema, + super::operation::RebaseErrorHandlingOption::ThrowError, + )?; + + input_selections.merge_into(std::iter::once(&edge_conditions))?; + + let new_node = FetchDependencyGraph::node_weight_mut(&mut dependency_graph.graph, new_node_id)?; + new_node.add_inputs( + &dependency_graph.supergraph_schema, + &wrap_input_selections( + &dependency_graph.supergraph_schema, + &input_type, + input_selections, + new_context, + ), + compute_input_rewrites_on_key_fetch( + &dependency_graph.supergraph_schema, + input_type.type_name(), + &dest_type, + ) + .into_iter() + .flatten(), + )?; + + // We also ensure to get the __typename of the current type in the "original" node. + let node = + FetchDependencyGraph::node_weight_mut(&mut dependency_graph.graph, stack_item.node_id)?; + let typename_field = Arc::new(OpPathElement::Field(Field::new_introspection_typename( + &dependency_graph.supergraph_schema, + &source_type, + None, + ))); + let typename_path = stack_item + .node_path + .path_in_node + .with_pushed(typename_field); + node.selection_set_mut().add_at_path(&typename_path, None)?; + Ok(ComputeNodesStackItem { + tree: &child.tree, + node_id: new_node_id, + node_path: stack_item + .node_path + .for_new_key_fetch(create_fetch_initial_path( + &dependency_graph.supergraph_schema, + &dest_type, + new_context, + )?), + context: new_context, + defer_context: updated_defer_context, + }) +} + +fn compute_nodes_for_root_type_resolution<'a>( + dependency_graph: &mut FetchDependencyGraph, + stack_item: &ComputeNodesStackItem<'_>, + child: &'a Arc>>, + edge_id: EdgeIndex, + edge: &crate::query_graph::QueryGraphEdge, + root_kind: SchemaRootDefinitionKind, + new_context: &'a OpGraphPathContext, +) -> Result, FederationError> { + if child.conditions.is_some() { + return Err(FederationError::internal(format!( + "Root type resolution edge {edge} should not have conditions" + ))); + } + let (source_id, dest_id) = stack_item.tree.graph.edge_endpoints(edge_id)?; + let source = stack_item.tree.graph.node_weight(source_id)?; + let dest = stack_item.tree.graph.node_weight(dest_id)?; + let source_type: ObjectTypeDefinitionPosition = source.type_.clone().try_into()?; + let dest_type: ObjectTypeDefinitionPosition = dest.type_.clone().try_into()?; + let root_operation_type = dependency_graph + .federated_query_graph + .schema_by_source(&dest.source)? + .schema() + .root_operation(root_kind.into()); + if root_operation_type != Some(&dest_type.type_name) { + return Err(FederationError::internal(format!( + "Expected {dest_type} to be the root {root_kind} type, \ + but that is {root_operation_type:?}" + ))); + } + + // Usually, we get here because a field (say `q`) has query root type as type, + // and the field queried for that root type is on another subgraph. + // When that happens, it means that on the original subgraph + // we may not have added _any_ subselection for type `q` + // and that would make the query to the original subgraph invalid. + // To avoid this, we request the __typename field. + // One exception however is if we're at the "top" of the current node + // (`path_in_node.is_empty()`, which is a corner case but can happen with @defer + // when everything in a query is deferred): + // in that case, there is no point in adding __typename + // because if we don't add any other selection, the node will be empty + // and we've rather detect that and remove the node entirely later. + let node = + FetchDependencyGraph::node_weight_mut(&mut dependency_graph.graph, stack_item.node_id)?; + if !stack_item.node_path.path_in_node.is_empty() { + let typename_field = Arc::new(OpPathElement::Field(Field::new_introspection_typename( + &dependency_graph.supergraph_schema, + &source_type.into(), + None, + ))); + let typename_path = stack_item + .node_path + .path_in_node + .with_pushed(typename_field); + node.selection_set_mut().add_at_path(&typename_path, None)?; + } + + // We take the edge, creating a new node. + // Note that we always create a new node because this corresponds to jumping subgraph + // after a field returned the query root type, + // and we want to preserve this ordering somewhat (debatable, possibly). + let updated_defer_context = stack_item.defer_context.after_subgraph_jump(); + let new_node_id = dependency_graph.new_root_type_node( + dest.source.clone(), + root_kind, + &dest_type, + Some(stack_item.node_path.response_path.clone()), + updated_defer_context.active_defer_ref.clone(), + )?; + dependency_graph.add_parent( + new_node_id, + ParentRelation { + parent_node_id: stack_item.node_id, + path_in_parent: Some(Arc::clone(&stack_item.node_path.path_in_node)), + }, + ); + Ok(ComputeNodesStackItem { + tree: &child.tree, + node_id: new_node_id, + node_path: stack_item + .node_path + .for_new_key_fetch(create_fetch_initial_path( + &dependency_graph.supergraph_schema, + &dest_type.into(), + new_context, + )?), + + context: new_context, + defer_context: updated_defer_context, + }) +} + +fn compute_nodes_for_op_path_element<'a>( + dependency_graph: &mut FetchDependencyGraph, + stack_item: &ComputeNodesStackItem<'a>, + child: &'a Arc>>, + operation: &OpPathElement, + created_nodes: &mut IndexSet, +) -> Result, FederationError> { + let Some(edge_id) = child.edge else { + // A null edge means that the operation does nothing + // but may contain directives to preserve. + // If it does contains directives, we look for @defer in particular. + // If we find it, this means that we should change our current node + // to one for the defer in question. + let (updated_operation, updated_defer_context) = extract_defer_from_operation( + dependency_graph, + operation, + &stack_item.defer_context, + &stack_item.node_path, + )?; + // We've now removed any @defer. + // If the operation contains other directives or a non-trivial type condition, + // we need to preserve it and so we add operation. + // Otherwise, we just skip it as a minor optimization (it makes the subgraph query + // slighly smaller and on complex queries, it might also deduplicate similar selections). + return Ok(ComputeNodesStackItem { + tree: &child.tree, + node_id: stack_item.node_id, + node_path: match updated_operation { + Some(op) if !op.directives().is_empty() => { + stack_item.node_path.add(Arc::new(op))? + } + _ => stack_item.node_path.clone(), + }, + context: stack_item.context, + defer_context: updated_defer_context, + }); + }; + let (source_id, dest_id) = stack_item.tree.graph.edge_endpoints(edge_id)?; + let source = stack_item.tree.graph.node_weight(source_id)?; + let dest = stack_item.tree.graph.node_weight(dest_id)?; + if source.source != dest.source { + return Err(FederationError::internal(format!( + "Collecting edge {edge_id:?} for {operation:?} \ + should not change the underlying subgraph" + ))); + } + + // We have a operation element, field or inline fragment. + // We first check if it's been "tagged" to remember that __typename must be queried. + // See the comment on the `optimize_sibling_typenames()` method to see why this exists. + if let Some(name) = operation.sibling_typename() { + // We need to add the query __typename for the current type in the current node. + // Note that `name` is the alias or '' if there is no alias + let alias = if name.is_empty() { + None + } else { + Some(name.clone()) + }; + let typename_field = Arc::new(OpPathElement::Field(Field::new_introspection_typename( + &dependency_graph.supergraph_schema, + &operation.parent_type_position(), + alias, + ))); + let typename_path = stack_item + .node_path + .path_in_node + .with_pushed(typename_field.clone()); + let node = + FetchDependencyGraph::node_weight_mut(&mut dependency_graph.graph, stack_item.node_id)?; + node.selection_set_mut().add_at_path(&typename_path, None)?; + dependency_graph.defer_tracking.update_subselection( + &DeferContext { + path_to_defer_parent: Arc::new( + stack_item + .defer_context + .path_to_defer_parent + .with_pushed(typename_field), + ), + ..stack_item.defer_context.clone() + }, + None, + )? + } + let Ok((Some(updated_operation), updated_defer_context)) = extract_defer_from_operation( + dependency_graph, + operation, + &stack_item.defer_context, + &stack_item.node_path, + ) else { + return Err(FederationError::internal(format!( + "Extracting @defer from {operation:?} should not have resulted in no operation" + ))); + }; + let mut updated = ComputeNodesStackItem { + tree: &child.tree, + node_id: stack_item.node_id, + node_path: stack_item.node_path.clone(), + context: stack_item.context, + defer_context: updated_defer_context, + }; + if let Some(conditions) = &child.conditions { + // We have @requires or some other dependency to create nodes for. + let (required_node_id, require_path) = handle_requires( + dependency_graph, + edge_id, + conditions, + (stack_item.node_id, &stack_item.node_path), + stack_item.context, + &updated.defer_context, + created_nodes, + )?; + updated.node_id = required_node_id; + updated.node_path = require_path; + } + if let OpPathElement::Field(field) = &updated_operation { + if *field.data().name() == TYPENAME_FIELD { + // Because of the optimization done in `QueryPlanner.optimizeSiblingTypenames`, + // we will rarely get an explicit `__typename` edge here. + // But one case where it can happen is where an @interfaceObject was involved, + // and we had to force jumping to another subgraph for getting the "true" `__typename`. + // However, this case can sometimes lead to fetch dependency node + // that only exists for that `__typename` resolution and that "looks" useless. + // That is, we could have a fetch dependency node that looks like: + // ``` + // Fetch(service: "Subgraph2") { + // { + // ... on I { + // __typename + // id + // } + // } => + // { + // ... on I { + // __typename + // } + // } + // } + // ``` + // but the trick is that the `__typename` in the input + // will be the name of the interface itself (`I` in this case) + // but the one return after the fetch will the name of the actual implementation + // (some implementation of `I`). + // *But* we later have optimizations that would remove such a node, + // on the node that the output is included in the input, + // which is in general the right thing to do + // (and genuinely ensure that some useless nodes created when handling + // complex @require gets eliminated). + // So we "protect" the node in this case to ensure + // that later optimization doesn't kick in in this case. + let updated_node = FetchDependencyGraph::node_weight_mut( + &mut dependency_graph.graph, + updated.node_id, + )?; + updated_node.must_preserve_selection_set = true + } + } + let edge = child.tree.graph.edge_weight(edge_id)?; + if let QueryGraphEdgeTransition::InterfaceObjectFakeDownCast { .. } = &edge.transition { + // We shouldn't add the operation "as is" as it's a down-cast but we're "faking it". + // However, if the operation has directives, we should preserve that. + let OpPathElement::InlineFragment(inline) = updated_operation else { + return Err(FederationError::internal(format!( + "Unexpected operation {updated_operation} for edge {edge}" + ))); + }; + if !inline.data().directives.is_empty() { + // We want to keep the directives, but we clear the condition + // since it's to a type that doesn't exists in the subgraph we're currently in. + updated.node_path = updated + .node_path + .add(Arc::new(inline.with_updated_type_condition(None).into()))?; + } + } else { + updated.node_path = updated.node_path.add(Arc::new(updated_operation))?; + } + Ok(updated) +} + +/// A helper function to wrap the `initial` value with nested conditions from `context`. +fn wrap_selection_with_type_and_conditions( + supergraph_schema: &ValidFederationSchema, + wrapping_type: &CompositeTypeDefinitionPosition, + context: &OpGraphPathContext, + initial: T, + mut wrap_in_fragment: impl FnMut(InlineFragment, T) -> T, +) -> T { + // PORT_NOTE: `unwrap` is used below, but the JS version asserts in `FragmentElement`'s constructor + // as well. However, there was a comment that we should add some validation, which is restated below. + // TODO: remove the `unwrap` with proper error handling, and ensure we have some intersection + // between the wrapping_type type and the new type condition. + let type_condition: CompositeTypeDefinitionPosition = supergraph_schema + .get_type(wrapping_type.type_name().clone()) + .unwrap() + .try_into() + .unwrap(); + + if context.is_empty() { + // PORT_NOTE: JS code looks for type condition in the wrapping type's schema based on + // the name of wrapping type. Not sure why. + return wrap_in_fragment( + InlineFragment::new(InlineFragmentData { + schema: supergraph_schema.clone(), + parent_type_position: wrapping_type.clone(), + type_condition_position: Some(type_condition.clone()), + directives: Default::default(), // None + selection_id: SelectionId::new(), + }), + initial, + ); + } + + // We wrap type-casts around `initial` value along with @include/@skip directive. + // Note that we use the same type condition on all nested fragments. However, + // except for the first one, we could well also use fragments with no type condition. + // The reason we do the former is mostly to preserve the older behavior, but the latter + // would technically produce slightly smaller query plans. + // TODO: Next major revision may consider changing this as stated above. + context.iter().fold(initial, |acc, cond| { + let directive = Directive { + name: cond.kind.name(), + arguments: vec![Argument { + name: name!("if"), + value: cond.value.clone().into(), + } + .into()], + }; + wrap_in_fragment( + InlineFragment::new(InlineFragmentData { + schema: supergraph_schema.clone(), + parent_type_position: wrapping_type.clone(), + type_condition_position: Some(type_condition.clone()), + directives: Arc::new([directive].into_iter().collect()), + selection_id: SelectionId::new(), + }), + acc, + ) + }) +} + +fn wrap_input_selections( + supergraph_schema: &ValidFederationSchema, + wrapping_type: &CompositeTypeDefinitionPosition, + selections: SelectionSet, + context: &OpGraphPathContext, +) -> SelectionSet { + wrap_selection_with_type_and_conditions( + supergraph_schema, + wrapping_type, + context, + selections, + |fragment, sub_selections| { + /* creates a new selection set of the form: + { + ... on { + + } + } + */ + let parent_type_position = fragment.data().parent_type_position.clone(); + let selection = Selection::from_inline_fragment(fragment, sub_selections); + SelectionSet::from_selection(parent_type_position, selection) + }, + ) +} + +fn create_fetch_initial_path( + supergraph_schema: &ValidFederationSchema, + dest_type: &CompositeTypeDefinitionPosition, + context: &OpGraphPathContext, +) -> Result, FederationError> { + // We make sure that all `OperationPath` are based on the supergraph as `OperationPath` is + // really about path on the input query/overall supergraph data (most other places already do + // this as the elements added to the operation path are from the input query, but this is + // an exception when we create an element from an type that may/usually will not be from the + // supergraph). Doing this make sure we can rely on things like checking subtyping between + // the types of a given path. + let rebased_type: CompositeTypeDefinitionPosition = supergraph_schema + .get_type(dest_type.type_name().clone()) + .and_then(|res| res.try_into())?; + Ok(Arc::new(wrap_selection_with_type_and_conditions( + supergraph_schema, + &rebased_type, + context, + Default::default(), + |fragment, sub_path| { + // Return an OpPath of the form: [, ...] + let front = vec![Arc::new(fragment.into())]; + OpPath(front.into_iter().chain(sub_path.0).collect()) + }, + ))) +} + +fn compute_input_rewrites_on_key_fetch( + supergraph_schema: &ValidFederationSchema, + input_type_name: &NodeStr, + dest_type: &CompositeTypeDefinitionPosition, +) -> Option>> { + // When we send a fetch to a subgraph, the inputs __typename must essentially match `dest_type` + // so the proper __resolveReference is called. If `dest_type` is a "normal" object type, that's + // going to be fine by default, but if `dest_type` is an interface in the supergraph (meaning + // that it is either an interface or an interface object), then the underlying object might + // have a __typename that is the concrete implementation type of the object, and we need to + // rewrite it. + if dest_type.is_interface_type() + || dest_type.is_interface_object_type(supergraph_schema.schema()) + { + // rewrite path: [ ... on , __typename ] + let type_cond = FetchDataPathElement::TypenameEquals(input_type_name.clone()); + let typename_field_elem = FetchDataPathElement::Key(TYPENAME_FIELD.into()); + let rewrite = FetchDataRewrite::ValueSetter(FetchDataValueSetter { + path: vec![type_cond, typename_field_elem], + set_value_to: dest_type.type_name().to_string().into(), + }); + Some(vec![Arc::new(rewrite)]) + } else { + None + } +} + +/// Returns an updated pair of (`operation`, `defer_context`) after the `defer` directive removed. +/// - The updated operation can be `None`, if operation is no longer necessary. +fn extract_defer_from_operation( + dependency_graph: &mut FetchDependencyGraph, + operation: &OpPathElement, + defer_context: &DeferContext, + node_path: &FetchDependencyGraphNodePath, +) -> Result<(Option, DeferContext), FederationError> { + let defer_args = operation.defer_directive_args(); + let Some(defer_args) = defer_args else { + let updated_path_to_defer_parent = defer_context + .path_to_defer_parent + .with_pushed(operation.clone().into()); + let updated_context = DeferContext { + path_to_defer_parent: updated_path_to_defer_parent.into(), + // Following fields are identical to those of `defer_context`. + current_defer_ref: defer_context.current_defer_ref.clone(), + active_defer_ref: defer_context.active_defer_ref.clone(), + is_part_of_query: defer_context.is_part_of_query, + }; + return Ok((Some(operation.clone()), updated_context)); + }; + + let updated_defer_ref = defer_args.label().ok_or_else(|| + // PORT_NOTE: The original TypeScript code has an assertion here. + FederationError::internal( + "All defers should have a label at this point", + ))?; + let updated_operation = operation.without_defer(); + let updated_path_to_defer_parent = match updated_operation { + None => Default::default(), // empty OpPath + Some(ref updated_operation) => OpPath(vec![Arc::new(updated_operation.clone())]), + }; + + dependency_graph.defer_tracking.register_defer( + defer_context, + &defer_args, + node_path.clone(), + operation.parent_type_position(), + )?; + + let updated_context = DeferContext { + current_defer_ref: Some(updated_defer_ref.into()), + path_to_defer_parent: updated_path_to_defer_parent.into(), + // Following fields are identical to those of `defer_context`. + active_defer_ref: defer_context.active_defer_ref.clone(), + is_part_of_query: defer_context.is_part_of_query, + }; + Ok((updated_operation, updated_context)) +} + +fn handle_requires( + _dependency_graph: &mut FetchDependencyGraph, + _edge_id: EdgeIndex, + _requires_conditions: &OpPathTree, + (_node_id, _node_path): (NodeIndex, &FetchDependencyGraphNodePath), + _context: &OpGraphPathContext, + _defer_context: &DeferContext, + _created_nodes: &mut IndexSet, +) -> Result<(NodeIndex, FetchDependencyGraphNodePath), FederationError> { + // PORT_NOTE: instead of returing IDs of created nodes they should be inserted directly + // in the `created_nodes` set passed by mutable reference. + todo!() // Port `handleRequires` (FED-25) +} diff --git a/apollo-federation/src/query_plan/fetch_dependency_graph_processor.rs b/apollo-federation/src/query_plan/fetch_dependency_graph_processor.rs new file mode 100644 index 0000000000..716b65fd2c --- /dev/null +++ b/apollo-federation/src/query_plan/fetch_dependency_graph_processor.rs @@ -0,0 +1,455 @@ +use std::collections::HashSet; + +use apollo_compiler::ast::Name; +use apollo_compiler::executable::VariableDefinition; +use apollo_compiler::Node; +use apollo_compiler::NodeStr; + +use crate::error::FederationError; +use crate::query_graph::QueryGraph; +use crate::query_plan::conditions::Conditions; +use crate::query_plan::fetch_dependency_graph::DeferredInfo; +use crate::query_plan::fetch_dependency_graph::FetchDependencyGraphNode; +use crate::query_plan::operation::RebasedFragments; +use crate::query_plan::operation::SelectionSet; +use crate::query_plan::ConditionNode; +use crate::query_plan::DeferNode; +use crate::query_plan::DeferredDeferBlock; +use crate::query_plan::DeferredDependency; +use crate::query_plan::ParallelNode; +use crate::query_plan::PlanNode; +use crate::query_plan::PrimaryDeferBlock; +use crate::query_plan::QueryPlanCost; +use crate::query_plan::SequenceNode; + +/// Constant used during query plan cost computation to account for the base cost of doing a fetch, +/// that is the fact any fetch imply some networking cost, request serialization/deserialization, +/// validation, ... +/// +/// The number is a little bit arbitrary, +/// but insofar as we roughly assign a cost of 1 to a single field queried +/// (see `selectionCost` method), +/// this can be though of as saying that resolving a single field is in general +/// a tiny fraction of the actual cost of doing a subgraph fetch. +const FETCH_COST: QueryPlanCost = 1000; + +/// Constant used during query plan cost computation +/// as a multiplier to the cost of fetches made in sequences. +/// +/// This means that if 3 fetches are done in sequence, +/// the cost of 1nd one is multiplied by this number, +/// the 2nd by twice this number, and the 3rd one by thrice this number. +/// The goal is to heavily favor query plans with the least amount of sequences, +/// since this affect overall latency directly. +/// The exact number is a tad arbitrary however. +const PIPELINING_COST: QueryPlanCost = 100; + +#[derive(Clone)] +pub(crate) struct FetchDependencyGraphToQueryPlanProcessor { + variable_definitions: Vec>, + fragments: Option, + operation_name: Option, + assigned_defer_labels: Option>, + counter: u32, +} + +/// Computes the cost of a Plan. +/// +/// A plan is essentially some mix of sequences and parallels of fetches. And the plan cost +/// is about minimizing both: +/// 1. The expected total latency of executing the plan. Typically, doing 2 fetches in +/// parallel will most likely have much better latency then executing those exact same +/// fetches in sequence, and so the cost of the latter must be greater than that of +/// the former. +/// 2. The underlying use of resources. For instance, if we query 2 fields and we have +/// the choice between getting those 2 fields from a single subgraph in 1 fetch, or +/// get each from a different subgraph with 2 fetches in parallel, then we want to +/// favor the former as just doing a fetch in and of itself has a cost in terms of +/// resources consumed. +/// +/// Do note that at the moment, this cost is solely based on the "shape" of the plan and has +/// to make some conservative assumption regarding concrete runtime behaviour. In particular, +/// it assumes that: +/// - all fields have the same cost (all resolvers take the same time). +/// - that field cost is relative small compare to actually doing a subgraph fetch. That is, +/// it assumes that the networking and other query processing costs are much higher than +/// the cost of resolving a single field. Or to put it more concretely, it assumes that +/// a fetch of 5 fields is probably not too different from than of 2 fields. +#[derive(Clone, Copy)] +pub(crate) struct FetchDependencyGraphToCostProcessor; + +/// Generic interface for "processing" a (reduced) dependency graph of fetch dependency nodes +/// (a `FetchDependencyGraph`). +/// +/// The processor methods will be called in a way that "respects" the dependency graph. +/// More precisely, a reduced fetch dependency graph can be expressed +/// as an alternance of parallel branches and sequences of nodes +/// (the roots needing to be either parallel or +/// sequential depending on whether we represent a `query` or a `mutation`), +/// and the processor will be called on nodes in such a way. +pub(crate) trait FetchDependencyGraphProcessor { + fn on_node( + &mut self, + query_graph: &QueryGraph, + node: &mut FetchDependencyGraphNode, + handled_conditions: &Conditions, + ) -> Result; + fn on_conditions(&mut self, conditions: &Conditions, value: TProcessed) -> TProcessed; + fn reduce_parallel(&mut self, values: impl IntoIterator) -> TProcessed; + fn reduce_sequence(&mut self, values: impl IntoIterator) -> TProcessed; + fn reduce_deferred( + &mut self, + defer_info: &DeferredInfo, + value: TProcessed, + ) -> Result; + fn reduce_defer( + &mut self, + main: TProcessed, + sub_selection: &SelectionSet, + deferred_blocks: Vec, + ) -> Result; +} + +// So you can use `&mut processor` as an `impl Processor`. +impl FetchDependencyGraphProcessor for &mut T +where + T: FetchDependencyGraphProcessor, +{ + fn on_node( + &mut self, + query_graph: &QueryGraph, + node: &mut FetchDependencyGraphNode, + handled_conditions: &Conditions, + ) -> Result { + (*self).on_node(query_graph, node, handled_conditions) + } + fn on_conditions(&mut self, conditions: &Conditions, value: TProcessed) -> TProcessed { + (*self).on_conditions(conditions, value) + } + fn reduce_parallel(&mut self, values: impl IntoIterator) -> TProcessed { + (*self).reduce_parallel(values) + } + fn reduce_sequence(&mut self, values: impl IntoIterator) -> TProcessed { + (*self).reduce_sequence(values) + } + fn reduce_deferred( + &mut self, + defer_info: &DeferredInfo, + value: TProcessed, + ) -> Result { + (*self).reduce_deferred(defer_info, value) + } + fn reduce_defer( + &mut self, + main: TProcessed, + sub_selection: &SelectionSet, + deferred_blocks: Vec, + ) -> Result { + (*self).reduce_defer(main, sub_selection, deferred_blocks) + } +} + +impl FetchDependencyGraphProcessor + for FetchDependencyGraphToCostProcessor +{ + /// The cost of a fetch roughly proportional to how many fields it fetches + /// (but see `selectionCost` for more details) + /// plus some constant "premium" to account for the fact than doing each fetch is costly + /// (and that fetch cost often dwarfted the actual cost of fields resolution). + fn on_node( + &mut self, + _query_graph: &QueryGraph, + node: &mut FetchDependencyGraphNode, + _handled_conditions: &Conditions, + ) -> Result { + Ok(FETCH_COST + node.cost()?) + } + + /// We don't take conditions into account in costing for now + /// as they don't really know anything on the condition + /// and this shouldn't really play a role in picking a plan over another. + fn on_conditions(&mut self, _conditions: &Conditions, value: QueryPlanCost) -> QueryPlanCost { + value + } + + /// We sum the cost of nodes in parallel. + /// Note that if we were only concerned about expected latency, + /// we could instead take the `max` of the values, + /// but as we also try to minimize general resource usage, + /// we want 2 parallel fetches with cost 1000 to be more costly + /// than one with cost 1000 and one with cost 10, + /// so suming is a simple option. + fn reduce_parallel( + &mut self, + values: impl IntoIterator, + ) -> QueryPlanCost { + parallel_cost(values) + } + + /// For sequences, we want to heavily favor "shorter" pipelines of fetches + /// as this directly impact the expected latency of the overall plan. + /// + /// To do so, each "stage" of a sequence/pipeline gets an additional multiplier + /// on the intrinsic cost of that stage. + fn reduce_sequence( + &mut self, + values: impl IntoIterator, + ) -> QueryPlanCost { + sequence_cost(values) + } + + /// This method exists so we can inject the necessary information for deferred block when + /// genuinely creating plan nodes. It's irrelevant to cost computation however and we just + /// return the cost of the block unchanged. + fn reduce_deferred( + &mut self, + _defer_info: &DeferredInfo, + value: QueryPlanCost, + ) -> Result { + Ok(value) + } + + /// It is unfortunately a bit difficult to properly compute costs for defers because in theory + /// some of the deferred blocks (the costs in `deferredValues`) can be started _before_ the full + /// `nonDeferred` part finishes (more precisely, the "structure" of query plans express the fact + /// that there is a non-deferred part and other deferred parts, but the complete dependency of + /// when a deferred part can be start is expressed through the `FetchNode.id` field, and as + /// this cost function is currently mainly based on the "structure" of query plans, we don't + /// have easy access to this info). + /// + /// Anyway, the approximation we make here is that all the deferred starts strictly after the + /// non-deferred one, and that all the deferred parts can be done in parallel. + fn reduce_defer( + &mut self, + main: QueryPlanCost, + _sub_selection: &SelectionSet, + deferred_blocks: Vec, + ) -> Result { + Ok(sequence_cost([main, parallel_cost(deferred_blocks)])) + } +} + +fn parallel_cost(values: impl IntoIterator) -> QueryPlanCost { + values.into_iter().sum() +} + +fn sequence_cost(values: impl IntoIterator) -> QueryPlanCost { + values + .into_iter() + .enumerate() + .map(|(i, stage)| stage * 1.max(i as QueryPlanCost * PIPELINING_COST)) + .sum() +} + +impl FetchDependencyGraphToQueryPlanProcessor { + pub(crate) fn new( + variable_definitions: Vec>, + fragments: Option, + operation_name: Option, + assigned_defer_labels: Option>, + ) -> Self { + Self { + variable_definitions, + fragments, + operation_name, + assigned_defer_labels, + counter: 0, + } + } +} + +impl FetchDependencyGraphProcessor, DeferredDeferBlock> + for FetchDependencyGraphToQueryPlanProcessor +{ + fn on_node( + &mut self, + query_graph: &QueryGraph, + node: &mut FetchDependencyGraphNode, + handled_conditions: &Conditions, + ) -> Result, FederationError> { + let op_name = self.operation_name.as_ref().map(|name| { + let counter = self.counter; + self.counter += 1; + let subgraph = to_valid_graphql_name(&node.subgraph_name).unwrap_or("".into()); + format!("{name}__{subgraph}__{counter}").into() + }); + node.to_plan_node( + query_graph, + handled_conditions, + &self.variable_definitions, + self.fragments.as_mut(), + op_name, + ) + } + + fn on_conditions( + &mut self, + conditions: &Conditions, + value: Option, + ) -> Option { + let mut value = value?; + match conditions { + Conditions::Boolean(condition) => { + // Note that currently `ConditionNode` only works for variables + // (`ConditionNode.condition` is expected to be a variable name and nothing else). + // We could change that, but really, why have a trivial `ConditionNode` + // when we can optimise things righ away. + condition.then_some(value) + } + Conditions::Variables(variables) => { + for (name, negated) in variables.iter() { + let (if_clause, else_clause) = if negated { + (None, Some(Box::new(value))) + } else { + (Some(Box::new(value)), None) + }; + value = PlanNode::from(ConditionNode { + condition_variable: name.clone(), + if_clause, + else_clause, + }); + } + Some(value) + } + } + } + + fn reduce_parallel( + &mut self, + values: impl IntoIterator>, + ) -> Option { + flat_wrap_nodes(NodeKind::Parallel, values) + } + + fn reduce_sequence( + &mut self, + values: impl IntoIterator>, + ) -> Option { + flat_wrap_nodes(NodeKind::Sequence, values) + } + + fn reduce_deferred( + &mut self, + defer_info: &DeferredInfo, + node: Option, + ) -> Result { + Ok(DeferredDeferBlock { + depends: defer_info + .dependencies + .iter() + .cloned() + .map(|id| DeferredDependency { id }) + .collect(), + label: if self + .assigned_defer_labels + .as_ref() + .is_some_and(|set| set.contains(&defer_info.label)) + { + None + } else { + Some(defer_info.label.clone()) + }, + query_path: defer_info.path.full_path.as_ref().try_into()?, + // Note that if the deferred block has nested @defer, + // then the `value` is going to be a `DeferNode` + // and we'll use it's own `subselection`, so we don't need it here. + sub_selection: if defer_info.deferred.is_empty() { + defer_info + .sub_selection + .without_empty_branches()? + .map(|filtered| filtered.as_ref().try_into()) + .transpose()? + } else { + None + }, + node: node.map(Box::new), + }) + } + + fn reduce_defer( + &mut self, + main: Option, + sub_selection: &SelectionSet, + deferred: Vec, + ) -> Result, FederationError> { + Ok(Some(PlanNode::Defer(DeferNode { + primary: PrimaryDeferBlock { + sub_selection: sub_selection + .without_empty_branches()? + .map(|filtered| filtered.as_ref().try_into()) + .transpose()?, + node: main.map(Box::new), + }, + deferred, + }))) + } +} + +/// Returns `None` if `subgraph_name` contains no character in [-_A-Za-z0-9] +/// +/// Add `.unwrap_or("".into())` to get an empty string in that case. +/// The empty string is not a valid name by itself but work if concatenating with something else. +pub(crate) fn to_valid_graphql_name(subgraph_name: &str) -> Option { + // We have almost no limitations on subgraph names, so we cannot use them inside query names + // without some cleaning up. GraphQL names can only be: [_A-Za-z][_0-9A-Za-z]*. + // To do so, we: + // 1. replace '-' by '_' because the former is not allowed but it's probably pretty + // common and using the later should be fairly readable. + // 2. remove any character in what remains that is not allowed. + // 3. Unsure the first character is not a number, and if it is, add a leading `_`. + // Note that this could theoretically lead to substantial changes to the name but should + // work well in practice (and if it's a huge problem for someone, we can change it). + let mut chars = subgraph_name.chars().filter_map(|c| { + if let '-' | '_' = c { + Some('_') + } else { + c.is_ascii_alphanumeric().then_some(c) + } + }); + let first = chars.next()?; + let mut sanitized = String::with_capacity(subgraph_name.len() + 1); + if first.is_ascii_digit() { + sanitized.push('_') + } + sanitized.push(first); + sanitized.extend(chars); + Some(sanitized) +} + +#[derive(Clone, Copy)] +enum NodeKind { + Parallel, + Sequence, +} + +/// Wraps the given nodes in a ParallelNode or SequenceNode, unless there's only +/// one node, in which case it is returned directly. Any nodes of the same kind +/// in the given list have their sub-nodes flattened into the list: ie, +/// flatWrapNodes('Sequence', [a, flatWrapNodes('Sequence', b, c), d]) returns a SequenceNode +/// with four children. +fn flat_wrap_nodes( + kind: NodeKind, + nodes: impl IntoIterator>, +) -> Option { + let mut iter = nodes.into_iter().flatten(); + let first = iter.next()?; + let Some(second) = iter.next() else { + return Some(first.clone()); + }; + let mut nodes = Vec::new(); + for node in [first, second].into_iter().chain(iter) { + match (kind, node) { + (NodeKind::Parallel, PlanNode::Parallel(inner)) => { + nodes.extend(inner.nodes.iter().cloned()) + } + (NodeKind::Sequence, PlanNode::Sequence(inner)) => { + nodes.extend(inner.nodes.iter().cloned()) + } + (_, node) => nodes.push(node), + } + } + Some(match kind { + NodeKind::Parallel => PlanNode::Parallel(ParallelNode { nodes }), + NodeKind::Sequence => PlanNode::Sequence(SequenceNode { nodes }), + }) +} diff --git a/apollo-federation/src/query_plan/generate.rs b/apollo-federation/src/query_plan/generate.rs new file mode 100644 index 0000000000..68d2d798df --- /dev/null +++ b/apollo-federation/src/query_plan/generate.rs @@ -0,0 +1,358 @@ +use std::collections::VecDeque; + +use crate::error::FederationError; +use crate::query_plan::QueryPlanCost; + +type Choices = Vec>; + +struct Partial { + partial_plan: Plan, + partial_cost: Option, + remaining: std::vec::IntoIter>, + is_root: bool, + index: Option, +} + +// PORT_NOTE: In TypeScript code, `generate_all_plans_and_find_best` takes three closures +// as arguments. However, more than one capture the QueryPlanningTraversal object and +// one of them mutably captures it, which is not allowed in Rust. Therefore, we use a trait +// that implements all three methods. +pub trait PlanBuilder { + /// `add_to_plan`: how to obtain a new plan by taking some plan and adding a new element to it. + fn add_to_plan(&mut self, plan: &Plan, elem: Element) -> Plan; + + /// `compute_plan_cost`: how to compute the cost of a plan. + fn compute_plan_cost(&mut self, plan: &mut Plan) -> Result; + + /// `on_plan`: an optional method called on every _complete_ plan generated by this method, + /// with both the cost of that plan and the best cost we have generated thus far + /// (if that's not the first plan generated). + /// This mostly exists to allow some debugging. + fn on_plan_generated(&self, plan: &Plan, cost: QueryPlanCost, prev_cost: Option); +} + +struct Extracted { + extracted: Element, + is_last: bool, +} + +/// Given some initial partial plan and a list of options for the remaining parts +/// that need to be added to that plan to make it complete, +/// this method "efficiently" generates (or at least evaluate) all the possible complete plans +/// and the returns the "best" one (the one with the lowest cost). +/// +/// Note that this method abstracts the actual types of both plans (type parameter `Plan`) +/// and additional elements to add to the plan (type parameter `Element`). +/// This is done for both the clarity and to make testing of this method easier. +/// +/// Type parameter `Plan` should be thought of as abstracting a query plan but in practice, +/// it is instantiated to a pair of a (`DependencyGraph`, corresponding `PathTree`). +/// +/// Type parameter `Element` should be thought of as an additional element +/// to add to the plan to make it complete. +/// It is instantiated in practice by a `PathTree` (for ... reasons ...) +/// that really correspond to a single `GraphPath`. +/// +/// As said above, this method takes 2 arguments: +/// - `initial` is a partial plan, +/// and corresponds to all the parts of the query being planned for which there no choices +/// (and theoretically can be empty, though very very rarely is in practice). +/// - `to_add` is the list of additional elements to add to `initial` +/// to make a full plan of the query being planned. +/// Each element of `to_add` corresponds to one of the query "leaf" +/// and is itself a list of all the possible options for that "leaf". +/// +/// In other words, a complete plan is obtained +/// by picking one choice in each of the element of `to_add` (so picking `to_add.len()` element) +/// and adding them all to `initial`. +/// The question being, which particular choice for each element of `to_add` yield the best plan. +/// +/// Of course, the total number of possible plans is the cartesian product of `to_add`, +/// which can get large, and so this method is trying to trim some of the options. +/// For that, the general idea is that we first generate one of the plan, compute its cost, +/// and then as we build other options, +/// we can check as we pick elements of `to_add` the cost of what we get, +/// and if we ever get a higher cost than the one fo the complete plan we already have, +/// then there is no point in checking the remaining elements, +/// and we can thus cut all the options for the remaining elements. +/// In other words, if a partial plan is ever already more costly +/// than another full plan we have computed, then adding more will never get us a better plan. +/// +/// Of course, this method is not guaranteed to save work, +/// and in the worst case, we'll still generate all plans. +/// But when a "good" plan is generated early, it can save a lot of computing work. +/// +/// And the 2nd "trick" of this method is that it starts by generating the plans +/// that correspond to picking choices in `to_add` at the same indexes, +/// and this because this often actually generate good plans. +/// The reason is that the order of choices for each element of `to_add` is not necessarily random, +/// because the algorithm generating paths is not random either. +/// In other words, elements at similar indexes have some good chance +/// to correspond to similar choices, and so will tend to correspond to good plans. +/// +/// Parameters: +/// * `initial`: the initial partial plan to use. +/// * `to_add`: a list of the remaining "elements" to add to `initial`. +/// Each element of `to_add` correspond to multiple choice +/// we can use to plan that particular element. +/// The `Option`s in side `type Choices = Vec>` are for internal use +/// and should all be `Some` when calling this function. +/// * `plan_builder`: a struct that implements the `PlanBuilder` trait. +pub fn generate_all_plans_and_find_best( + mut initial: Plan, + to_add: Vec>, + plan_builder: &mut impl PlanBuilder, +) -> Result<(Plan, QueryPlanCost), FederationError> +where + Element: Clone, + // Uncomment to use `dbg!()` + // Element: std::fmt::Debug, + // Plan: std::fmt::Debug, +{ + if to_add.is_empty() { + let cost = plan_builder.compute_plan_cost(&mut initial)?; + return Ok((initial, cost)); + } + // Note: we save ourselves the computation of the cost of `initial` + // (we pass no `partial_cost` in this initialization). + // That's because `partial_cost` is about exiting early when we've found at least one full plan + // and the cost of that plan is already smaller than the cost of a partial computation. + // But any plan is going be built from `initial` with at least one 'choice' added to it, + // all plans are guaranteed to be more costly than `initial` anyway. + // Note that save for `initial`, + // we always compute `partialCost` as the pros of exiting some branches early are large enough + // that it outweight computing some costs unecessarily from time to time. + let mut stack = VecDeque::new(); + stack.push_back(Partial { + partial_plan: initial, + partial_cost: None, + remaining: to_add.into_iter(), + is_root: true, + index: Some(0), + }); + + let mut min = None; + while let Some(Partial { + partial_plan, + partial_cost, + mut remaining, + is_root, + index, + }) = stack.pop_back() + { + // If we've found some plan already, + // and the partial we have is already more costly than that, + // then no point continuing with it. + if let (Some((_, min_cost)), Some(partial_cost)) = (&min, &partial_cost) { + if partial_cost >= min_cost { + continue; + } + } + + // Does not panic as we only ever insert in the stack with non-empty `remaining` + let next_choices = &mut remaining.as_mut_slice()[0]; + + let picked_index = pick_next(index, next_choices); + let Extracted { extracted, is_last } = extract(picked_index, next_choices); + let mut new_partial_plan = plan_builder.add_to_plan(&partial_plan, extracted); + let cost = plan_builder.compute_plan_cost(&mut new_partial_plan)?; + + if !is_last { + // First, re-insert what correspond to all the choices not in `extracted`. + insert_in_stack( + &mut stack, + Partial { + partial_plan, + partial_cost, + is_root, + index: index.and_then(|i| { + let next = i + 1; + (is_root && next < next_choices.len()).then_some(next) + }), + // TODO: can we avoid cloning? + remaining: remaining.clone(), + }, + ) + } + // Done for now with `next_choices` + remaining.next(); + + let previous_min_cost = min.as_ref().map(|&(_, cost)| cost); + let previous_min_is_better = previous_min_cost.is_some_and(|min| min <= cost); + if remaining.as_slice().is_empty() { + // We have a complete plan. If it is best, save it, otherwise, we're done with it. + plan_builder.on_plan_generated(&new_partial_plan, cost, previous_min_cost); + if !previous_min_is_better { + min = Some((new_partial_plan, cost)) + } + continue; + } + + if !previous_min_is_better { + insert_in_stack( + &mut stack, + Partial { + partial_plan: new_partial_plan, + partial_cost: Some(cost), + remaining, + is_root: false, + index, + }, + ) + } + } + min.ok_or_else(|| FederationError::internal("A plan should have been found")) +} + +fn insert_in_stack( + stack: &mut VecDeque>, + item: Partial, +) { + // We push elements with a fixed index at the end so they are handled first. + if item.index.is_some() { + stack.push_back(item) + } else { + stack.push_front(item) + } +} + +fn pick_next(opt_index: Option, remaining: &Choices) -> usize { + if let Some(index) = opt_index { + if let Some(choice) = remaining.get(index) { + assert!(choice.is_some(), "Invalid index {index}"); + return index; + } + } + remaining + .iter() + .position(|choice| choice.is_some()) + .expect("Passed a `remaining` with all `None`") +} + +fn extract(index: usize, choices: &mut Choices) -> Extracted { + let extracted = choices[index].take().unwrap(); + let is_last = choices.iter().all(|choice| choice.is_none()); + Extracted { extracted, is_last } +} + +#[cfg(test)] +mod tests { + use super::*; + + type Element = &'static str; + type Plan = Vec; + + struct TestPlanBuilder<'a> { + generated: &'a mut Vec>, + target_len: usize, + } + + impl<'a> PlanBuilder for TestPlanBuilder<'a> { + fn add_to_plan(&mut self, partial_plan: &Plan, new_element: Element) -> Plan { + let new_plan: Plan = partial_plan + .iter() + .cloned() + .chain(std::iter::once(new_element)) + .collect(); + if new_plan.len() == self.target_len { + self.generated.push(new_plan.clone()) + } + new_plan + } + + fn compute_plan_cost(&mut self, plan: &mut Plan) -> Result { + Ok(plan + .iter() + .map(|element| element.len() as QueryPlanCost) + .sum()) + } + + fn on_plan_generated( + &self, + _plan: &Plan, + _cost: QueryPlanCost, + _prev_cost: Option, + ) { + } + } + + /// Returns (best, generated) + fn generate_test_plans(initial: Plan, choices: Vec>>) -> (Plan, Vec) { + let mut generated = Vec::new(); + let target_len = initial.len() + choices.len(); + + let mut plan_builder = TestPlanBuilder { + generated: &mut generated, + target_len, + }; + let (best, _) = + generate_all_plans_and_find_best::(initial, choices, &mut plan_builder) + .unwrap(); + (best, generated) + } + + #[test] + fn pick_elements_at_same_index_first() { + let (best, generated) = generate_test_plans( + vec!["I"], + vec![ + vec![Some("A1"), Some("B1")], + vec![Some("A2"), Some("B2")], + vec![Some("A3"), Some("B3")], + ], + ); + assert_eq!(best, ["I", "A1", "A2", "A3"]); + assert_eq!(generated[0], ["I", "A1", "A2", "A3"]); + assert_eq!(generated[1], ["I", "B1", "B2", "B3"]); + } + + #[test] + fn bail_early_for_more_costly_elements() { + let (best, generated) = generate_test_plans( + vec!["I"], + vec![ + vec![Some("A1"), Some("B1VeryCostly")], + vec![Some("A2"), Some("B2Co")], + vec![Some("A3"), Some("B3")], + ], + ); + + assert_eq!(best, ["I", "A1", "A2", "A3"]); + // We should ignore plans with both B1 and B2 due there cost. So we should have just 2 plans. + assert_eq!(generated.len(), 2); + assert_eq!(generated[0], ["I", "A1", "A2", "A3"]); + assert_eq!(generated[1], ["I", "A1", "A2", "B3"]); + } + + #[test] + fn handles_branches_of_various_sizes() { + let (best, mut generated) = generate_test_plans( + vec!["I"], + vec![ + vec![Some("A1x"), Some("B1")], + vec![Some("A2"), Some("B2Costly"), Some("C2")], + vec![Some("A3")], + vec![Some("A4"), Some("B4")], + ], + ); + + assert_eq!(best, ["I", "B1", "A2", "A3", "A4"]); + // We don't want to rely on ordering + // (the tests ensures we get the best plan that we want, and the rest doesn't matter). + generated.sort(); + // We should generate every option, except those including `B2Costly` + assert_eq!( + generated, + [ + vec!["I", "A1x", "A2", "A3", "A4"], + vec!["I", "A1x", "A2", "A3", "B4"], + vec!["I", "A1x", "C2", "A3", "A4"], + vec!["I", "A1x", "C2", "A3", "B4"], + vec!["I", "B1", "A2", "A3", "A4"], + vec!["I", "B1", "A2", "A3", "B4"], + vec!["I", "B1", "C2", "A3", "A4"], + vec!["I", "B1", "C2", "A3", "B4"], + ], + ); + } +} diff --git a/apollo-federation/src/query_plan/mod.rs b/apollo-federation/src/query_plan/mod.rs new file mode 100644 index 0000000000..7a27999455 --- /dev/null +++ b/apollo-federation/src/query_plan/mod.rs @@ -0,0 +1,251 @@ +use std::sync::Arc; + +use apollo_compiler::executable; +use apollo_compiler::executable::Name; +use apollo_compiler::validation::Valid; +use apollo_compiler::ExecutableDocument; +use apollo_compiler::NodeStr; + +use crate::query_plan::query_planner::QueryPlanningStatistics; + +pub(crate) mod conditions; +pub(crate) mod display; +pub(crate) mod fetch_dependency_graph; +pub(crate) mod fetch_dependency_graph_processor; +pub mod generate; +pub(crate) mod operation; +pub mod query_planner; +pub(crate) mod query_planning_traversal; + +pub type QueryPlanCost = i64; + +#[derive(Debug, Default)] +pub struct QueryPlan { + pub node: Option, + pub statistics: QueryPlanningStatistics, +} + +#[derive(Debug, derive_more::From)] +pub enum TopLevelPlanNode { + Subscription(SubscriptionNode), + #[from(types(FetchNode))] + Fetch(Box), + Sequence(SequenceNode), + Parallel(ParallelNode), + Flatten(FlattenNode), + Defer(DeferNode), + #[from(types(ConditionNode))] + Condition(Box), +} + +#[derive(Debug, Clone)] +pub struct SubscriptionNode { + pub primary: Box, + // XXX(@goto-bus-stop) Is this not just always a SequenceNode? + pub rest: Option>, +} + +#[derive(Debug, Clone, derive_more::From)] +pub enum PlanNode { + #[from(types(FetchNode))] + Fetch(Box), + Sequence(SequenceNode), + Parallel(ParallelNode), + Flatten(FlattenNode), + Defer(DeferNode), + #[from(types(ConditionNode))] + Condition(Box), +} + +#[derive(Debug, Clone)] +pub struct FetchNode { + pub subgraph_name: NodeStr, + /// Optional identifier for the fetch for defer support. All fetches of a given plan will be + /// guaranteed to have a unique `id`. + pub id: Option, + pub variable_usages: Vec, + /// `Selection`s in apollo-rs _can_ have a `FragmentSpread`, but this `Selection` is + /// specifically typing the `requires` key in a built query plan, where there can't be + /// `FragmentSpread`. + // PORT_NOTE: This was its own type in the JS codebase, but it's likely simpler to just have the + // constraint be implicit for router instead of creating a new type. + pub requires: Option>, + // PORT_NOTE: We don't serialize the "operation" string in this struct, as these query plan + // nodes are meant for direct consumption by router (without any serdes), so we leave the + // question of whether it needs to be serialized to router. + pub operation_document: Valid, + pub operation_name: Option, + pub operation_kind: executable::OperationType, + /// Optionally describe a number of "rewrites" that query plan executors should apply to the + /// data that is sent as the input of this fetch. Note that such rewrites should only impact the + /// inputs of the fetch they are applied to (meaning that, as those inputs are collected from + /// the current in-memory result, the rewrite should _not_ impact said in-memory results, only + /// what is sent in the fetch). + pub input_rewrites: Arc>>, + /// Similar to `input_rewrites`, but for optional "rewrites" to apply to the data that is + /// received from a fetch (and before it is applied to the current in-memory results). + pub output_rewrites: Vec>, +} + +#[derive(Debug, Clone)] +pub struct SequenceNode { + pub nodes: Vec, +} + +#[derive(Debug, Clone)] +pub struct ParallelNode { + pub nodes: Vec, +} + +#[derive(Debug, Clone)] +pub struct FlattenNode { + pub path: Vec, + pub node: Box, +} + +/// A `DeferNode` corresponds to one or more `@defer` applications at the same level of "nestedness" +/// in the planned query. +/// +/// It contains a "primary block" and a vector of "deferred blocks". The "primary block" represents +/// the part of the query that is _not_ deferred (so the part of the query up until we reach the +/// @defer(s) this handles), while each "deferred block" correspond to the deferred part of one of +/// the @defer(s) handled by the node. +/// +/// Note that `DeferNode`s are only generated if defer support is enabled for the query planner. +/// Also note that if said support is enabled, then `DeferNode`s are always generated if the query +/// has a @defer application, even if in some cases generated plan may not "truly" defer the +/// underlying fetches (i.e. in cases where `deferred[*].node` are all undefined). This currently +/// happens because some specific cases of defer cannot be handled, but could later also happen if +/// we implement more advanced server-side heuristics to decide if deferring is judicious or not. +/// This allows the executor of the plan to consistently send a defer-abiding multipart response to +/// the client. +#[derive(Debug, Clone)] +pub struct DeferNode { + /// The "primary" part of a defer, that is the non-deferred part (though could be deferred + /// itself for a nested defer). + pub primary: PrimaryDeferBlock, + /// The "deferred" parts of the defer (note that it's a vector). Each of those deferred elements + /// will correspond to a different chunk of the response to the client (after the initial + /// on-deferred one that is). + pub deferred: Vec, +} + +/// The primary block of a `DeferNode`. +#[derive(Debug, Clone)] +pub struct PrimaryDeferBlock { + /// The part of the original query that "selects" the data to send in that primary response + /// once the plan in `node` completes). Note that if the parent `DeferNode` is nested, then it + /// must come inside the `DeferredNode` in which it is nested, and in that case this + /// sub-selection will start at that parent `DeferredNode.query_path`. Note that this can be + /// `None` in the rare case that everything in the original query is deferred (which is not very + /// useful in practice, but not disallowed by the @defer spec at the moment). + pub sub_selection: Option, + /// The plan to get all the data for the primary block. Same notes as for subselection: usually + /// defined, but can be undefined in some corner cases where nothing is to be done in the + /// primary block. + pub node: Option>, +} + +/// A deferred block of a `DeferNode`. +#[derive(Debug, Clone)] +pub struct DeferredDeferBlock { + /// References one or more fetch node(s) (by `id`) within `DeferNode.primary.node`. The plan of + /// this deferred part should not be started until all such fetches return. + pub depends: Vec, + /// The optional defer label. + pub label: Option, + /// Path, in the query, to the `@defer` application this corresponds to. The `sub_selection` + /// starts at this `query_path`. + pub query_path: Vec, + /// The part of the original query that "selects" the data to send in the deferred response + /// (once the plan in `node` completes). Will be set _unless_ `node` is a `DeferNode` itself. + pub sub_selection: Option, + /// The plan to get all the data for this deferred block. Usually set, but can be `None` for a + /// `@defer` application where everything has been fetched in the "primary block" (i.e. when + /// this deferred block only exists to expose what should be send to the upstream client in a + /// deferred response), but without declaring additional fetches. This happens for @defer + /// applications that cannot be handled through the query planner and where the defer cannot be + /// passed through to the subgraph). + pub node: Option>, +} + +#[derive(Debug, Clone)] +pub struct DeferredDependency { + /// A `FetchNode` ID. + pub id: NodeStr, +} + +#[derive(Debug, Clone)] +pub struct ConditionNode { + pub condition_variable: Name, + pub if_clause: Option>, + pub else_clause: Option>, +} + +/// The type of rewrites currently supported on the input/output data of fetches. +/// +/// A rewrite usually identifies some sub-part of the data and some action to perform on that +/// sub-part. +#[derive(Debug, Clone)] +pub enum FetchDataRewrite { + ValueSetter(FetchDataValueSetter), + KeyRenamer(FetchDataKeyRenamer), +} + +/// A rewrite that sets a value at the provided path of the data it is applied to. +#[derive(Debug, Clone)] +pub struct FetchDataValueSetter { + /// Path to the value that is set by this "rewrite". + pub path: Vec, + /// The value to set at `path`. Note that the query planner currently only uses string values, + /// but that may change in the future. + pub set_value_to: serde_json_bytes::Value, +} + +/// A rewrite that renames the key at the provided path of the data it is applied to. +#[derive(Debug, Clone)] +pub struct FetchDataKeyRenamer { + /// Path to the key that is renamed by this "rewrite". + pub path: Vec, + /// The key to rename to at `path`. + pub rename_key_to: NodeStr, +} + +/// Vectors of this element match path(s) to a value in fetch data. Each element is (1) a key in +/// object data, (2) _any_ index in array data (often serialized as `@`), or (3) a typename +/// constraint on the object data at that point in the path(s) (a path should only match for objects +/// whose `__typename` is the provided type). +/// +/// It's possible for vectors of this element to match no paths in fetch data, e.g. if an object key +/// doesn't exist, or if an object's `__typename` doesn't equal the provided one. If this occurs, +/// then query plan execution should not execute the instruction this path is associated with. +/// +/// The path starts at the top of the data it is applied to. So for instance, for fetch data inputs, +/// the path starts at the root of the object representing those inputs. +/// +/// Note that the `@` is currently optional in some contexts, as query plan execution may assume +/// upon encountering array data in a path that it should match the remaining path to the array's +/// elements. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum FetchDataPathElement { + Key(NodeStr), + AnyIndex, + TypenameEquals(NodeStr), +} + +/// Vectors of this element match a path in a query. Each element is (1) a field in a query, or (2) +/// an inline fragment in a query. +#[derive(Debug, Clone)] +pub enum QueryPathElement { + Field(executable::Field), + InlineFragment(executable::InlineFragment), +} + +impl QueryPlan { + fn new(node: impl Into, statistics: QueryPlanningStatistics) -> Self { + Self { + node: Some(node.into()), + statistics, + } + } +} diff --git a/apollo-federation/src/query_plan/operation.rs b/apollo-federation/src/query_plan/operation.rs new file mode 100644 index 0000000000..02ce57356e --- /dev/null +++ b/apollo-federation/src/query_plan/operation.rs @@ -0,0 +1,7422 @@ +//! GraphQL operation types for apollo-federation. +//! +//! ## Selection types +//! Each "conceptual" type consists of up to three actual types: a data type, an "element" +//! type, and a selection type. +//! - The data type records the data about the type. Things like a field name or fragment type +//! condition are in the data type. These types can be constructed and modified with plain rust. +//! - The element type contains the data type and maintains a key for the data. These types provide +//! APIs for modifications that keep the key up-to-date. +//! - The selection type contains the element type and, for composite fields, a subselection. +//! +//! For example, for fields, the data type is [`FieldData`], the element type is +//! [`Field`], and the selection type is [`FieldSelection`]. + +use std::borrow::Cow; +use std::collections::HashMap; +use std::collections::HashSet; +use std::fmt::Display; +use std::fmt::Formatter; +use std::hash::Hash; +use std::ops::Deref; +use std::sync::atomic; +use std::sync::Arc; +use std::sync::OnceLock; + +use apollo_compiler::executable; +use apollo_compiler::executable::Name; +use apollo_compiler::name; +use apollo_compiler::validation::Valid; +use apollo_compiler::Node; +use apollo_compiler::NodeStr; +use indexmap::IndexMap; +use indexmap::IndexSet; + +use crate::error::FederationError; +use crate::error::SingleFederationError; +use crate::error::SingleFederationError::Internal; +use crate::link::federation_spec_definition::get_federation_spec_definition_from_subgraph; +use crate::query_graph::graph_path::OpPathElement; +use crate::query_plan::conditions::Conditions; +use crate::query_plan::FetchDataKeyRenamer; +use crate::query_plan::FetchDataPathElement; +use crate::query_plan::FetchDataRewrite; +use crate::schema::definitions::is_composite_type; +use crate::schema::definitions::types_can_be_merged; +use crate::schema::definitions::AbstractType; +use crate::schema::position::CompositeTypeDefinitionPosition; +use crate::schema::position::InterfaceTypeDefinitionPosition; +use crate::schema::position::ObjectTypeDefinitionPosition; +use crate::schema::position::SchemaRootDefinitionKind; +use crate::schema::ValidFederationSchema; + +pub(crate) const TYPENAME_FIELD: Name = name!("__typename"); + +// Global storage for the counter used to uniquely identify selections +static NEXT_ID: atomic::AtomicUsize = atomic::AtomicUsize::new(1); + +/// Opaque wrapper of the unique selection ID type. +/// +/// Note that we shouldn't add `derive(Serialize, Deserialize)` to this without changing the types +/// to be something like UUIDs. +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub(crate) struct SelectionId(usize); + +impl SelectionId { + pub(crate) fn new() -> Self { + // atomically increment global counter + Self(NEXT_ID.fetch_add(1, atomic::Ordering::AcqRel)) + } +} + +/// Compare two input values, with two special cases for objects: assuming no duplicate keys, +/// and order-independence. +/// +/// This comes from apollo-rs: https://github.com/apollographql/apollo-rs/blob/6825be88fe13cd0d67b83b0e4eb6e03c8ab2555e/crates/apollo-compiler/src/validation/selection.rs#L160-L188 +/// Hopefully we can do this more easily in the future! +fn same_value(left: &executable::Value, right: &executable::Value) -> bool { + use apollo_compiler::executable::Value; + match (left, right) { + (Value::Null, Value::Null) => true, + (Value::Enum(left), Value::Enum(right)) => left == right, + (Value::Variable(left), Value::Variable(right)) => left == right, + (Value::String(left), Value::String(right)) => left == right, + (Value::Float(left), Value::Float(right)) => left == right, + (Value::Int(left), Value::Int(right)) => left == right, + (Value::Boolean(left), Value::Boolean(right)) => left == right, + (Value::List(left), Value::List(right)) => left + .iter() + .zip(right.iter()) + .all(|(left, right)| same_value(left, right)), + (Value::Object(left), Value::Object(right)) if left.len() == right.len() => { + left.iter().all(|(key, value)| { + right + .iter() + .find(|(other_key, _)| key == other_key) + .is_some_and(|(_, other_value)| same_value(value, other_value)) + }) + } + _ => false, + } +} + +/// Returns true if two argument lists are equivalent. +/// +/// The arguments and values must be the same, independent of order. +fn same_arguments( + left: &[Node], + right: &[Node], +) -> bool { + if left.len() != right.len() { + return false; + } + + let right = right + .iter() + .map(|arg| (&arg.name, arg)) + .collect::>(); + + left.iter().all(|arg| { + right + .get(&arg.name) + .is_some_and(|right_arg| same_value(&arg.value, &right_arg.value)) + }) +} + +/// Returns true if two directive lists are equivalent. +fn same_directives(left: &executable::DirectiveList, right: &executable::DirectiveList) -> bool { + if left.len() != right.len() { + return false; + } + + left.iter().all(|left_directive| { + right.iter().any(|right_directive| { + left_directive.name == right_directive.name + && same_arguments(&left_directive.arguments, &right_directive.arguments) + }) + }) +} + +/// An analogue of the apollo-compiler type `Operation` with these changes: +/// - Stores the schema that the operation is queried against. +/// - Swaps `operation_type` with `root_kind` (using the analogous federation-next type). +/// - Encloses collection types in `Arc`s to facilitate cheaper cloning. +/// - Stores the fragments used by this operation (the executable document the operation was taken +/// from may contain other fragments that are not used by this operation). +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Operation { + pub(crate) schema: ValidFederationSchema, + pub(crate) root_kind: SchemaRootDefinitionKind, + pub(crate) name: Option, + pub(crate) variables: Arc>>, + pub(crate) directives: Arc, + pub(crate) selection_set: SelectionSet, + pub(crate) named_fragments: NamedFragments, +} + +pub(crate) struct NormalizedDefer { + pub operation: Operation, + pub has_defers: bool, + pub assigned_defer_labels: HashSet, + pub defer_conditions: IndexMap>, +} + +impl Operation { + /// Parse an operation from a source string. + #[cfg(any(test, doc))] + pub fn parse( + schema: ValidFederationSchema, + source_text: &str, + source_name: &str, + operation_name: Option<&str>, + ) -> Result { + let document = apollo_compiler::ExecutableDocument::parse_and_validate( + schema.schema(), + source_text, + source_name, + )?; + Operation::from_operation_document(schema, &document, operation_name) + } + + pub fn from_operation_document( + schema: ValidFederationSchema, + document: &Valid, + operation_name: Option<&str>, + ) -> Result { + let operation = document.get_operation(operation_name).map_err(|_| { + FederationError::internal(format!("No operation named {operation_name:?}")) + })?; + let named_fragments = NamedFragments::new(&document.fragments, &schema); + let selection_set = + SelectionSet::from_selection_set(&operation.selection_set, &named_fragments, &schema)?; + Ok(Operation { + schema, + root_kind: operation.operation_type.into(), + name: operation.name.clone(), + variables: Arc::new(operation.variables.clone()), + directives: Arc::new(operation.directives.clone()), + selection_set, + named_fragments, + }) + } + + // PORT_NOTE(@goto-bus-stop): It might make sense for the returned data structure to *be* the + // `DeferNormalizer` from the JS side + pub(crate) fn with_normalized_defer(self) -> NormalizedDefer { + if self.has_defer() { + todo!("@defer not implemented"); + } else { + NormalizedDefer { + operation: self, + has_defers: false, + assigned_defer_labels: HashSet::new(), + defer_conditions: IndexMap::new(), + } + } + } + + fn has_defer(&self) -> bool { + self.selection_set.has_defer() + || self + .named_fragments + .fragments + .values() + .any(|f| f.has_defer()) + } + + pub(crate) fn without_defer(self) -> Self { + if self.has_defer() { + todo!("@defer not implemented"); + } + + self + } +} + +/// An analogue of the apollo-compiler type `SelectionSet` with these changes: +/// - For the type, stores the schema and the position in that schema instead of just the +/// `NamedType`. +/// - Stores selections in a map so they can be normalized efficiently. +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct SelectionSet { + pub(crate) schema: ValidFederationSchema, + pub(crate) type_position: CompositeTypeDefinitionPosition, + pub(crate) selections: Arc, +} + +pub(crate) mod normalized_selection_map { + use std::borrow::Cow; + use std::iter::Map; + use std::ops::Deref; + use std::sync::Arc; + + use apollo_compiler::ast::Name; + use indexmap::IndexMap; + + use crate::error::FederationError; + use crate::error::SingleFederationError::Internal; + use crate::query_plan::operation::normalized_field_selection::FieldSelection; + use crate::query_plan::operation::normalized_fragment_spread_selection::FragmentSpreadSelection; + use crate::query_plan::operation::normalized_inline_fragment_selection::InlineFragmentSelection; + use crate::query_plan::operation::HasSelectionKey; + use crate::query_plan::operation::Selection; + use crate::query_plan::operation::SelectionKey; + use crate::query_plan::operation::SelectionSet; + + /// A "normalized" selection map is an optimized representation of a selection set which does + /// not contain selections with the same selection "key". Selections that do have the same key + /// are merged during the normalization process. By storing a selection set as a map, we can + /// efficiently merge/join multiple selection sets. + /// + /// Because the key depends strictly on the value, we expose the underlying map's API in a + /// read-only capacity, while mutations use an API closer to `IndexSet`. We don't just use an + /// `IndexSet` since key computation is expensive (it involves sorting). This type is in its own + /// module to prevent code from accidentally mutating the underlying map outside the mutation + /// API. + #[derive(Debug, Clone, PartialEq, Eq, Default)] + pub(crate) struct SelectionMap(IndexMap); + + impl Deref for SelectionMap { + type Target = IndexMap; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + impl SelectionMap { + pub(crate) fn new() -> Self { + SelectionMap(IndexMap::new()) + } + + pub(crate) fn clear(&mut self) { + self.0.clear(); + } + + pub(crate) fn insert(&mut self, value: Selection) -> Option { + self.0.insert(value.key(), value) + } + + /// Insert a selection at a specific index. + pub(crate) fn insert_at(&mut self, index: usize, value: Selection) -> Option { + self.0.shift_insert(index, value.key(), value) + } + + /// Remove a selection from the map. Returns the selection and its numeric index. + pub(crate) fn remove(&mut self, key: &SelectionKey) -> Option<(usize, Selection)> { + // We specifically use shift_remove() instead of swap_remove() to maintain order. + self.0 + .shift_remove_full(key) + .map(|(index, _key, selection)| (index, selection)) + } + + pub(crate) fn retain( + &mut self, + mut predicate: impl FnMut(&SelectionKey, &Selection) -> bool, + ) { + self.0.retain(|k, v| predicate(k, v)) + } + + pub(crate) fn get_mut(&mut self, key: &SelectionKey) -> Option { + self.0.get_mut(key).map(SelectionValue::new) + } + + pub(crate) fn iter_mut(&mut self) -> IterMut { + self.0.iter_mut().map(|(k, v)| (k, SelectionValue::new(v))) + } + + pub(super) fn entry(&mut self, key: SelectionKey) -> Entry { + match self.0.entry(key) { + indexmap::map::Entry::Occupied(entry) => Entry::Occupied(OccupiedEntry(entry)), + indexmap::map::Entry::Vacant(entry) => Entry::Vacant(VacantEntry(entry)), + } + } + + pub(crate) fn extend(&mut self, other: SelectionMap) { + self.0.extend(other.0) + } + + pub(crate) fn extend_ref(&mut self, other: &SelectionMap) { + self.0 + .extend(other.iter().map(|(k, v)| (k.clone(), v.clone()))) + } + + /// Returns the selection set resulting from "recursively" filtering any selection + /// that does not match the provided predicate. + /// This method calls `predicate` on every selection of the selection set, + /// not just top-level ones, and apply a "depth-first" strategy: + /// when the predicate is called on a given selection it is guaranteed that + /// filtering has happened on all the selections of its sub-selection. + pub(crate) fn filter_recursive_depth_first( + &self, + predicate: &mut dyn FnMut(&Selection) -> Result, + ) -> Result, FederationError> { + fn recur_sub_selections<'sel>( + selection: &'sel Selection, + predicate: &mut dyn FnMut(&Selection) -> Result, + ) -> Result, FederationError> { + Ok(match selection { + Selection::Field(field) => { + if let Some(sub_selections) = &field.selection_set { + match sub_selections.filter_recursive_depth_first(predicate)? { + Cow::Borrowed(_) => Cow::Borrowed(selection), + Cow::Owned(new) => Cow::Owned(Selection::from_field( + field.field.clone(), + Some(new), + )), + } + } else { + Cow::Borrowed(selection) + } + } + Selection::InlineFragment(fragment) => match fragment + .selection_set + .filter_recursive_depth_first(predicate)? + { + Cow::Borrowed(_) => Cow::Borrowed(selection), + Cow::Owned(selection_set) => Cow::Owned(Selection::InlineFragment( + Arc::new(InlineFragmentSelection { + inline_fragment: fragment.inline_fragment.clone(), + selection_set, + }), + )), + }, + Selection::FragmentSpread(_) => { + return Err(FederationError::internal("unexpected fragment spread")) + } + }) + } + let mut iter = self.0.iter(); + let mut enumerated = (&mut iter).enumerate(); + let mut new_map: IndexMap<_, _>; + loop { + let Some((index, (key, selection))) = enumerated.next() else { + return Ok(Cow::Borrowed(self)); + }; + let filtered = recur_sub_selections(selection, predicate)?; + let keep = predicate(&filtered)?; + if keep && matches!(filtered, Cow::Borrowed(_)) { + // Nothing changed so far, continue without cloning + continue; + } + + // Clone the map so far + new_map = self.0.as_slice()[..index] + .iter() + .map(|(k, v)| (k.clone(), v.clone())) + .collect(); + + if keep { + new_map.insert(key.clone(), filtered.into_owned()); + } + break; + } + for (key, selection) in iter { + let filtered = recur_sub_selections(selection, predicate)?; + if predicate(&filtered)? { + new_map.insert(key.clone(), filtered.into_owned()); + } + } + Ok(Cow::Owned(Self(new_map))) + } + } + + impl FromIterator for SelectionMap + where + A: Into, + { + fn from_iter>(iter: T) -> Self { + let mut map = Self::new(); + for selection in iter { + map.insert(selection.into()); + } + map + } + } + + type IterMut<'a> = Map< + indexmap::map::IterMut<'a, SelectionKey, Selection>, + fn((&'a SelectionKey, &'a mut Selection)) -> (&'a SelectionKey, SelectionValue<'a>), + >; + + /// A mutable reference to a `Selection` value in a `SelectionMap`, which + /// also disallows changing key-related data (to maintain the invariant that a value's key is + /// the same as it's map entry's key). + #[derive(Debug)] + pub(crate) enum SelectionValue<'a> { + Field(FieldSelectionValue<'a>), + FragmentSpread(FragmentSpreadSelectionValue<'a>), + InlineFragment(InlineFragmentSelectionValue<'a>), + } + + impl<'a> SelectionValue<'a> { + pub(crate) fn new(selection: &'a mut Selection) -> Self { + match selection { + Selection::Field(field_selection) => { + SelectionValue::Field(FieldSelectionValue::new(field_selection)) + } + Selection::FragmentSpread(fragment_spread_selection) => { + SelectionValue::FragmentSpread(FragmentSpreadSelectionValue::new( + fragment_spread_selection, + )) + } + Selection::InlineFragment(inline_fragment_selection) => { + SelectionValue::InlineFragment(InlineFragmentSelectionValue::new( + inline_fragment_selection, + )) + } + } + } + } + + #[derive(Debug)] + pub(crate) struct FieldSelectionValue<'a>(&'a mut Arc); + + impl<'a> FieldSelectionValue<'a> { + pub(crate) fn new(field_selection: &'a mut Arc) -> Self { + Self(field_selection) + } + + pub(crate) fn get(&self) -> &Arc { + self.0 + } + + pub(crate) fn get_sibling_typename_mut(&mut self) -> &mut Option { + Arc::make_mut(self.0).field.sibling_typename_mut() + } + + pub(crate) fn get_selection_set_mut(&mut self) -> &mut Option { + &mut Arc::make_mut(self.0).selection_set + } + } + + #[derive(Debug)] + pub(crate) struct FragmentSpreadSelectionValue<'a>(&'a mut Arc); + + impl<'a> FragmentSpreadSelectionValue<'a> { + pub(crate) fn new(fragment_spread_selection: &'a mut Arc) -> Self { + Self(fragment_spread_selection) + } + + pub(crate) fn get(&self) -> &Arc { + self.0 + } + } + + #[derive(Debug)] + pub(crate) struct InlineFragmentSelectionValue<'a>(&'a mut Arc); + + impl<'a> InlineFragmentSelectionValue<'a> { + pub(crate) fn new(inline_fragment_selection: &'a mut Arc) -> Self { + Self(inline_fragment_selection) + } + + pub(crate) fn get(&self) -> &Arc { + self.0 + } + + pub(crate) fn get_selection_set_mut(&mut self) -> &mut SelectionSet { + &mut Arc::make_mut(self.0).selection_set + } + } + + pub(crate) enum Entry<'a> { + Occupied(OccupiedEntry<'a>), + Vacant(VacantEntry<'a>), + } + + impl<'a> Entry<'a> { + pub fn or_insert( + self, + produce: impl FnOnce() -> Result, + ) -> Result, FederationError> { + match self { + Self::Occupied(entry) => Ok(entry.into_mut()), + Self::Vacant(entry) => entry.insert(produce()?), + } + } + } + + pub(crate) struct OccupiedEntry<'a>(indexmap::map::OccupiedEntry<'a, SelectionKey, Selection>); + + impl<'a> OccupiedEntry<'a> { + pub(crate) fn get(&self) -> &Selection { + self.0.get() + } + + pub(crate) fn get_mut(&mut self) -> SelectionValue { + SelectionValue::new(self.0.get_mut()) + } + + pub(crate) fn into_mut(self) -> SelectionValue<'a> { + SelectionValue::new(self.0.into_mut()) + } + + pub(crate) fn key(&self) -> &SelectionKey { + self.0.key() + } + + pub(crate) fn remove(self) -> Selection { + // We specifically use shift_remove() instead of swap_remove() to maintain order. + self.0.shift_remove() + } + } + + pub(crate) struct VacantEntry<'a>(indexmap::map::VacantEntry<'a, SelectionKey, Selection>); + + impl<'a> VacantEntry<'a> { + pub(crate) fn key(&self) -> &SelectionKey { + self.0.key() + } + + pub(crate) fn insert( + self, + value: Selection, + ) -> Result, FederationError> { + if *self.key() != value.key() { + return Err(Internal { + message: format!( + "Key mismatch when inserting selection {} into vacant entry ", + value + ), + } + .into()); + } + Ok(SelectionValue::new(self.0.insert(value))) + } + } + + impl IntoIterator for SelectionMap { + type Item = as IntoIterator>::Item; + type IntoIter = as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + as IntoIterator>::into_iter(self.0) + } + } +} + +pub(crate) use normalized_selection_map::FieldSelectionValue; +pub(crate) use normalized_selection_map::FragmentSpreadSelectionValue; +pub(crate) use normalized_selection_map::InlineFragmentSelectionValue; +pub(crate) use normalized_selection_map::SelectionMap; +pub(crate) use normalized_selection_map::SelectionValue; + +/// A selection "key" (unrelated to the federation `@key` directive) is an identifier of a selection +/// (field, inline fragment, or fragment spread) that is used to determine whether two selections +/// can be merged. +/// +/// In order to merge two selections they need to +/// * reference the same field/inline fragment +/// * specify the same directives +/// * directives have to be applied in the same order +/// * directive arguments order does not matter (they get automatically sorted by their names). +/// * selection cannot specify @defer directive +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub(crate) enum SelectionKey { + Field { + /// The field alias (if specified) or field name in the resulting selection set. + response_name: Name, + /// directives applied on the field + directives: Arc, + }, + FragmentSpread { + /// The name of the fragment. + fragment_name: Name, + /// Directives applied on the fragment spread (does not contain @defer). + directives: Arc, + }, + InlineFragment { + /// The optional type condition of the fragment. + type_condition: Option, + /// Directives applied on the fragment spread (does not contain @defer). + directives: Arc, + }, + Defer { + /// Unique selection ID used to distinguish deferred fragment spreads that cannot be merged. + deferred_id: SelectionId, + }, +} + +impl SelectionKey { + fn is_typename_field(&self) -> bool { + matches!(self, SelectionKey::Field { response_name, .. } if *response_name == TYPENAME_FIELD) + } +} + +pub(crate) trait HasSelectionKey { + fn key(&self) -> SelectionKey; +} + +/// Options for the `.containment()` family of selection functions. +#[derive(Debug, Clone, Copy)] +pub struct ContainmentOptions { + /// If the right-hand side has a __typename selection but the left-hand side does not, + /// still consider the left-hand side to contain the right-hand side. + pub ignore_missing_typename: bool, +} + +// Currently Default *can* be derived, but if we add a new option +// here, that might no longer be true. +#[allow(clippy::derivable_impls)] +impl Default for ContainmentOptions { + fn default() -> Self { + Self { + ignore_missing_typename: false, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Containment { + /// The left-hand selection does not fully contain right-hand selection. + NotContained, + /// The left-hand selection fully contains the right-hand selection, and more. + StrictlyContained, + /// Two selections are equal. + Equal, +} +impl Containment { + /// Returns true if the right-hand selection set is strictly contained or equal. + pub fn is_contained(self) -> bool { + matches!(self, Containment::StrictlyContained | Containment::Equal) + } +} + +/// An analogue of the apollo-compiler type `Selection` that stores our other selection analogues +/// instead of the apollo-compiler types. +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) enum Selection { + Field(Arc), + FragmentSpread(Arc), + InlineFragment(Arc), +} + +impl Selection { + pub(crate) fn from_field(field: Field, sub_selections: Option) -> Self { + Self::Field(Arc::new(field.with_subselection(sub_selections))) + } + + pub(crate) fn from_inline_fragment( + inline_fragment: InlineFragment, + sub_selections: SelectionSet, + ) -> Self { + let inline_fragment_selection = InlineFragmentSelection { + inline_fragment, + selection_set: sub_selections, + }; + Self::InlineFragment(Arc::new(inline_fragment_selection)) + } + + pub(crate) fn from_element( + element: OpPathElement, + sub_selections: Option, + ) -> Result { + // PORT_NOTE: This is TODO item is copied from the JS `selectionOfElement` function. + // TODO: validate that the subSelection is ok for the element + match element { + OpPathElement::Field(field) => Ok(Self::from_field(field, sub_selections)), + OpPathElement::InlineFragment(inline_fragment) => { + let Some(sub_selections) = sub_selections else { + return Err(FederationError::internal( + "unexpected inline fragment without sub-selections", + )); + }; + Ok(Self::from_inline_fragment(inline_fragment, sub_selections)) + } + } + } + + pub(crate) fn schema(&self) -> &ValidFederationSchema { + match self { + Selection::Field(field_selection) => &field_selection.field.data().schema, + Selection::FragmentSpread(fragment_spread_selection) => { + &fragment_spread_selection.spread.data().schema + } + Selection::InlineFragment(inline_fragment_selection) => { + &inline_fragment_selection.inline_fragment.data().schema + } + } + } + + fn directives(&self) -> &Arc { + match self { + Selection::Field(field_selection) => &field_selection.field.data().directives, + Selection::FragmentSpread(fragment_spread_selection) => { + &fragment_spread_selection.spread.data().directives + } + Selection::InlineFragment(inline_fragment_selection) => { + &inline_fragment_selection.inline_fragment.data().directives + } + } + } + + pub(crate) fn element(&self) -> Result { + match self { + Selection::Field(field_selection) => { + Ok(OpPathElement::Field(field_selection.field.clone())) + } + Selection::FragmentSpread(_) => Err(Internal { + message: "Fragment spread does not have element".to_owned(), + } + .into()), + Selection::InlineFragment(inline_fragment_selection) => Ok( + OpPathElement::InlineFragment(inline_fragment_selection.inline_fragment.clone()), + ), + } + } + + pub(crate) fn selection_set(&self) -> Result, FederationError> { + match self { + Selection::Field(field_selection) => Ok(field_selection.selection_set.as_ref()), + Selection::FragmentSpread(_) => Err(Internal { + message: "Fragment spread does not directly have a selection set".to_owned(), + } + .into()), + Selection::InlineFragment(inline_fragment_selection) => { + Ok(Some(&inline_fragment_selection.selection_set)) + } + } + } + + pub(crate) fn conditions(&self) -> Result { + let self_conditions = Conditions::from_directives(self.directives())?; + if let Conditions::Boolean(false) = self_conditions { + // Never included, so there is no point recursing. + Ok(Conditions::Boolean(false)) + } else { + match self { + Selection::Field(_) => { + // The sub-selections of this field don't affect whether we should query this + // field, so we explicitly do not merge them in. + // + // PORT_NOTE: The JS codebase merges the sub-selections' conditions in with the + // field's conditions when field's selections are non-boolean. This is arguably + // a bug, so we've fixed it here. + Ok(self_conditions) + } + Selection::InlineFragment(inline) => { + Ok(self_conditions.merge(inline.selection_set.conditions()?)) + } + Selection::FragmentSpread(_x) => Err(FederationError::internal( + "Unexpected fragment spread in Selection::conditions()", + )), + } + } + } + + pub(crate) fn collect_variables<'selection>( + &'selection self, + variables: &mut HashSet<&'selection Name>, + ) -> Result<(), FederationError> { + match self { + Selection::Field(field) => field.collect_variables(variables), + Selection::InlineFragment(inline_fragment) => { + inline_fragment.collect_variables(variables) + } + Selection::FragmentSpread(_) => { + Err(FederationError::internal("unexpected fragment spread")) + } + } + } + + pub(crate) fn has_defer(&self) -> bool { + match self { + Selection::Field(field_selection) => field_selection.has_defer(), + Selection::FragmentSpread(fragment_spread_selection) => { + fragment_spread_selection.has_defer() + } + Selection::InlineFragment(inline_fragment_selection) => { + inline_fragment_selection.has_defer() + } + } + } + + fn collect_used_fragment_names(&self, aggregator: &mut HashMap) { + match self { + Selection::Field(field_selection) => { + if let Some(s) = field_selection.selection_set.clone() { + s.collect_used_fragment_names(aggregator) + } + } + Selection::InlineFragment(inline) => { + inline.selection_set.collect_used_fragment_names(aggregator); + } + Selection::FragmentSpread(fragment) => { + let current_count = aggregator + .entry(fragment.spread.data().fragment_name.clone()) + .or_default(); + *current_count += 1; + } + } + } + + pub(crate) fn rebase_on( + &self, + parent_type: &CompositeTypeDefinitionPosition, + named_fragments: &NamedFragments, + schema: &ValidFederationSchema, + error_handling: RebaseErrorHandlingOption, + ) -> Result, FederationError> { + match self { + Selection::Field(field) => { + field.rebase_on(parent_type, named_fragments, schema, error_handling) + } + Selection::FragmentSpread(spread) => { + spread.rebase_on(parent_type, named_fragments, schema, error_handling) + } + Selection::InlineFragment(inline) => { + inline.rebase_on(parent_type, named_fragments, schema, error_handling) + } + } + } + + pub(crate) fn can_add_to( + &self, + parent_type: &CompositeTypeDefinitionPosition, + schema: &ValidFederationSchema, + ) -> bool { + match self { + Selection::Field(field) => field.can_add_to(parent_type, schema), + Selection::FragmentSpread(_) => true, + Selection::InlineFragment(inline) => inline.can_add_to(parent_type, schema), + } + } + + pub(crate) fn normalize( + &self, + parent_type: &CompositeTypeDefinitionPosition, + named_fragments: &NamedFragments, + schema: &ValidFederationSchema, + option: NormalizeSelectionOption, + ) -> Result, FederationError> { + match self { + Selection::Field(field) => { + field.normalize(parent_type, named_fragments, schema, option) + } + Selection::FragmentSpread(spread) => { + spread.normalize(parent_type, named_fragments, schema) + } + Selection::InlineFragment(inline) => { + inline.normalize(parent_type, named_fragments, schema, option) + } + } + } + + pub(crate) fn with_updated_selection_set( + &self, + selection_set: Option, + ) -> Result { + match self { + Selection::Field(field) => Ok(Selection::from( + field.with_updated_selection_set(selection_set), + )), + Selection::InlineFragment(inline_fragment) => Ok(Selection::from( + inline_fragment.with_updated_selection_set(selection_set), + )), + Selection::FragmentSpread(_) => { + Err(FederationError::internal("unexpected fragment spread")) + } + } + } + + pub(crate) fn containment( + &self, + other: &Selection, + options: ContainmentOptions, + ) -> Containment { + match (self, other) { + (Selection::Field(self_field), Selection::Field(other_field)) => { + self_field.containment(other_field, options) + } + ( + Selection::InlineFragment(self_fragment), + Selection::InlineFragment(_) | Selection::FragmentSpread(_), + ) => self_fragment.containment(other, options), + ( + Selection::FragmentSpread(self_fragment), + Selection::InlineFragment(_) | Selection::FragmentSpread(_), + ) => self_fragment.containment(other, options), + _ => Containment::NotContained, + } + } + + /// Returns true if this selection is a superset of the other selection. + pub(crate) fn contains(&self, other: &Selection) -> bool { + self.containment(other, Default::default()).is_contained() + } + + /// Apply the `mapper` to self.selection_set, if it exists, and return a new `Selection`. + /// - Note: The returned selection may have no subselection set or an empty one if the mapper + /// returns so, which may make the returned selection invalid. It's caller's responsibility + /// to appropriately handle invalid return values. + pub(crate) fn map_selection_set( + &self, + mapper: impl FnOnce(&SelectionSet) -> Result, FederationError>, + ) -> Result { + if let Some(selection_set) = self.selection_set()? { + self.with_updated_selection_set(mapper(selection_set)?) + } else { + // selection has no (sub-)selection set. + Ok(self.clone()) + } + } +} + +impl From for Selection { + fn from(value: FieldSelection) -> Self { + Self::Field(value.into()) + } +} + +impl From for Selection { + fn from(value: FragmentSpreadSelection) -> Self { + Self::FragmentSpread(value.into()) + } +} + +impl From for Selection { + fn from(value: InlineFragmentSelection) -> Self { + Self::InlineFragment(value.into()) + } +} + +impl HasSelectionKey for Selection { + fn key(&self) -> SelectionKey { + match self { + Selection::Field(field_selection) => field_selection.key(), + Selection::FragmentSpread(fragment_spread_selection) => fragment_spread_selection.key(), + Selection::InlineFragment(inline_fragment_selection) => inline_fragment_selection.key(), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, derive_more::From)] +pub(crate) enum SelectionOrSet { + Selection(Selection), + SelectionSet(SelectionSet), +} + +/// An analogue of the apollo-compiler type `Fragment` with these changes: +/// - Stores the type condition explicitly, which means storing the schema and position (in +/// apollo-compiler, this is in the `SelectionSet`). +/// - Encloses collection types in `Arc`s to facilitate cheaper cloning. +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct Fragment { + pub(crate) schema: ValidFederationSchema, + pub(crate) name: Name, + pub(crate) type_condition_position: CompositeTypeDefinitionPosition, + pub(crate) directives: Arc, + pub(crate) selection_set: SelectionSet, +} + +impl Fragment { + fn from_fragment( + fragment: &executable::Fragment, + named_fragments: &NamedFragments, + schema: &ValidFederationSchema, + ) -> Result { + Ok(Self { + schema: schema.clone(), + name: fragment.name.clone(), + type_condition_position: schema + .get_type(fragment.type_condition().clone())? + .try_into()?, + directives: Arc::new(fragment.directives.clone()), + selection_set: SelectionSet::from_selection_set( + &fragment.selection_set, + named_fragments, + schema, + )?, + }) + } + + // PORT NOTE: in JS code this is stored on the fragment + fn fragment_usages(&self) -> HashMap { + let mut usages = HashMap::new(); + self.selection_set.collect_used_fragment_names(&mut usages); + usages + } + + // PORT NOTE: in JS code this is stored on the fragment + fn collect_used_fragment_names(&self, aggregator: &mut HashMap) { + self.selection_set.collect_used_fragment_names(aggregator) + } + + fn has_defer(&self) -> bool { + self.selection_set.has_defer() + } +} + +mod normalized_field_selection { + use std::collections::HashSet; + use std::sync::Arc; + + use apollo_compiler::executable; + use apollo_compiler::executable::Name; + use apollo_compiler::Node; + + use crate::error::FederationError; + use crate::query_graph::graph_path::OpPathElement; + use crate::query_plan::operation::directives_with_sorted_arguments; + use crate::query_plan::operation::HasSelectionKey; + use crate::query_plan::operation::SelectionKey; + use crate::query_plan::operation::SelectionSet; + use crate::query_plan::FetchDataPathElement; + use crate::schema::position::CompositeTypeDefinitionPosition; + use crate::schema::position::FieldDefinitionPosition; + use crate::schema::position::TypeDefinitionPosition; + use crate::schema::ValidFederationSchema; + + /// An analogue of the apollo-compiler type `Field` with these changes: + /// - Makes the selection set optional. This is because `SelectionSet` requires a type of + /// `CompositeTypeDefinitionPosition`, which won't exist for fields returning a non-composite type + /// (scalars and enums). + /// - Stores the field data (other than the selection set) in `Field`, to facilitate + /// operation paths and graph paths. + /// - For the field definition, stores the schema and the position in that schema instead of just + /// the `FieldDefinition` (which contains no references to the parent type or schema). + /// - Encloses collection types in `Arc`s to facilitate cheaper cloning. + #[derive(Debug, Clone, PartialEq, Eq)] + pub(crate) struct FieldSelection { + pub(crate) field: Field, + pub(crate) selection_set: Option, + } + + impl HasSelectionKey for FieldSelection { + fn key(&self) -> SelectionKey { + self.field.key() + } + } + + impl FieldSelection { + pub(crate) fn with_updated_selection_set( + &self, + selection_set: Option, + ) -> Self { + Self { + field: self.field.clone(), + selection_set, + } + } + + pub(crate) fn element(&self) -> OpPathElement { + OpPathElement::Field(self.field.clone()) + } + + pub(crate) fn with_updated_alias(&self, alias: Name) -> Field { + let mut data = self.field.data().clone(); + data.alias = Some(alias); + Field::new(data) + } + + pub(crate) fn collect_variables<'selection>( + &'selection self, + variables: &mut HashSet<&'selection Name>, + ) -> Result<(), FederationError> { + self.field.collect_variables(variables); + if let Some(set) = &self.selection_set { + set.collect_variables(variables)? + } + Ok(()) + } + } + + /// The non-selection-set data of `FieldSelection`, used with operation paths and graph + /// paths. + #[derive(Clone, PartialEq, Eq, Hash)] + pub(crate) struct Field { + data: FieldData, + key: SelectionKey, + } + + impl std::fmt::Debug for Field { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.data.fmt(f) + } + } + + impl Field { + pub(crate) fn new(data: FieldData) -> Self { + Self { + key: data.key(), + data, + } + } + + /// Create a trivial field selection without any arguments or directives. + pub(crate) fn from_position( + schema: &ValidFederationSchema, + field_position: FieldDefinitionPosition, + ) -> Self { + Self::new(FieldData::from_position(schema, field_position)) + } + + pub(crate) fn new_introspection_typename( + schema: &ValidFederationSchema, + parent_type: &CompositeTypeDefinitionPosition, + alias: Option, + ) -> Self { + Self::new(FieldData { + schema: schema.clone(), + field_position: parent_type.introspection_typename_field(), + alias, + arguments: Default::default(), + directives: Default::default(), + sibling_typename: None, + }) + } + + /// Turn this `Field` into a `FieldSelection` with the given sub-selection. If this is + /// meant to be a leaf selection, use `None`. + pub(crate) fn with_subselection( + self, + selection_set: Option, + ) -> FieldSelection { + FieldSelection { + field: self, + selection_set, + } + } + + pub(crate) fn schema(&self) -> &ValidFederationSchema { + &self.data.schema + } + + pub(crate) fn data(&self) -> &FieldData { + &self.data + } + + pub(crate) fn sibling_typename(&self) -> Option<&Name> { + self.data.sibling_typename.as_ref() + } + + pub(crate) fn sibling_typename_mut(&mut self) -> &mut Option { + &mut self.data.sibling_typename + } + + pub(crate) fn with_updated_directives( + &self, + directives: executable::DirectiveList, + ) -> Field { + let mut data = self.data.clone(); + data.directives = Arc::new(directives); + Self::new(data) + } + + pub(crate) fn as_path_element(&self) -> FetchDataPathElement { + FetchDataPathElement::Key(self.data().response_name().into()) + } + + pub(crate) fn collect_variables<'selection>( + &'selection self, + variables: &mut HashSet<&'selection Name>, + ) { + for arg in self.data().arguments.iter() { + collect_variables_from_argument(arg, variables) + } + for dir in self.data().directives.iter() { + collect_variables_from_directive(dir, variables) + } + } + } + + pub(crate) fn collect_variables_from_argument<'selection>( + argument: &'selection executable::Argument, + variables: &mut HashSet<&'selection Name>, + ) { + if let Some(v) = argument.value.as_variable() { + variables.insert(v); + } + } + + pub(crate) fn collect_variables_from_directive<'selection>( + directive: &'selection executable::Directive, + variables: &mut HashSet<&'selection Name>, + ) { + for arg in directive.arguments.iter() { + collect_variables_from_argument(arg, variables) + } + } + + impl HasSelectionKey for Field { + fn key(&self) -> SelectionKey { + self.key.clone() + } + } + + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + pub(crate) struct FieldData { + pub(crate) schema: ValidFederationSchema, + pub(crate) field_position: FieldDefinitionPosition, + pub(crate) alias: Option, + pub(crate) arguments: Arc>>, + pub(crate) directives: Arc, + pub(crate) sibling_typename: Option, + } + + impl FieldData { + /// Create a trivial field selection without any arguments or directives. + pub fn from_position( + schema: &ValidFederationSchema, + field_position: FieldDefinitionPosition, + ) -> Self { + Self { + schema: schema.clone(), + field_position, + alias: None, + arguments: Default::default(), + directives: Default::default(), + sibling_typename: None, + } + } + + pub(crate) fn name(&self) -> &Name { + self.field_position.field_name() + } + + pub(crate) fn response_name(&self) -> Name { + self.alias.clone().unwrap_or_else(|| self.name().clone()) + } + + pub(crate) fn output_base_type(&self) -> Result { + let definition = self.field_position.get(self.schema.schema())?; + self.schema + .get_type(definition.ty.inner_named_type().clone()) + } + + pub(crate) fn is_leaf(&self) -> Result { + let base_type_position = self.output_base_type()?; + Ok(matches!( + base_type_position, + TypeDefinitionPosition::Scalar(_) | TypeDefinitionPosition::Enum(_) + )) + } + } + + impl HasSelectionKey for FieldData { + fn key(&self) -> SelectionKey { + SelectionKey::Field { + response_name: self.response_name(), + directives: Arc::new(directives_with_sorted_arguments(&self.directives)), + } + } + } +} + +pub(crate) use normalized_field_selection::Field; +pub(crate) use normalized_field_selection::FieldData; +pub(crate) use normalized_field_selection::FieldSelection; + +mod normalized_fragment_spread_selection { + use std::sync::Arc; + + use apollo_compiler::executable; + use apollo_compiler::executable::Name; + + use crate::query_plan::operation::directives_with_sorted_arguments; + use crate::query_plan::operation::is_deferred_selection; + use crate::query_plan::operation::HasSelectionKey; + use crate::query_plan::operation::SelectionId; + use crate::query_plan::operation::SelectionKey; + use crate::query_plan::operation::SelectionSet; + use crate::schema::position::CompositeTypeDefinitionPosition; + use crate::schema::ValidFederationSchema; + + #[derive(Debug, Clone, PartialEq, Eq)] + pub(crate) struct FragmentSpreadSelection { + pub(crate) spread: FragmentSpread, + pub(crate) selection_set: SelectionSet, + } + + impl HasSelectionKey for FragmentSpreadSelection { + fn key(&self) -> SelectionKey { + self.spread.key() + } + } + + /// An analogue of the apollo-compiler type `FragmentSpread` with these changes: + /// - Stores the schema (may be useful for directives). + /// - Encloses collection types in `Arc`s to facilitate cheaper cloning. + #[derive(Clone, PartialEq, Eq)] + pub(crate) struct FragmentSpread { + data: FragmentSpreadData, + key: SelectionKey, + } + + impl std::fmt::Debug for FragmentSpread { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.data.fmt(f) + } + } + + impl FragmentSpread { + pub(crate) fn new(data: FragmentSpreadData) -> Self { + Self { + key: data.key(), + data, + } + } + + pub(crate) fn data(&self) -> &FragmentSpreadData { + &self.data + } + } + + impl HasSelectionKey for FragmentSpread { + fn key(&self) -> SelectionKey { + self.key.clone() + } + } + + #[derive(Debug, Clone, PartialEq, Eq)] + pub(crate) struct FragmentSpreadData { + pub(crate) schema: ValidFederationSchema, + pub(crate) fragment_name: Name, + pub(crate) type_condition_position: CompositeTypeDefinitionPosition, + // directives applied on the fragment spread selection + pub(crate) directives: Arc, + // directives applied within the fragment definition + // + // PORT_NOTE: The JS codebase combined the fragment spread's directives with the fragment + // definition's directives. This was invalid GraphQL as those directives may not be applicable + // on different locations. While we now keep track of those references, they are currently ignored. + pub(crate) fragment_directives: Arc, + pub(crate) selection_id: SelectionId, + } + + impl HasSelectionKey for FragmentSpreadData { + fn key(&self) -> SelectionKey { + if is_deferred_selection(&self.directives) { + SelectionKey::Defer { + deferred_id: self.selection_id.clone(), + } + } else { + SelectionKey::FragmentSpread { + fragment_name: self.fragment_name.clone(), + directives: Arc::new(directives_with_sorted_arguments(&self.directives)), + } + } + } + } +} + +pub(crate) use normalized_fragment_spread_selection::FragmentSpread; +pub(crate) use normalized_fragment_spread_selection::FragmentSpreadData; +pub(crate) use normalized_fragment_spread_selection::FragmentSpreadSelection; + +impl FragmentSpreadSelection { + pub(crate) fn rebase_on( + &self, + parent_type: &CompositeTypeDefinitionPosition, + named_fragments: &NamedFragments, + schema: &ValidFederationSchema, + error_handling: RebaseErrorHandlingOption, + ) -> Result, FederationError> { + // We preserve the parent type here, to make sure we don't lose context, but we actually don't + // want to expand the spread as that would compromise the code that optimize subgraph fetches to re-use named + // fragments. + // + // This is a little bit iffy, because the fragment may not apply at this parent type, but we + // currently leave it to the caller to ensure this is not a mistake. But most of the + // QP code works on selections with fully expanded fragments, so this code (and that of `can_add_to` + // on come into play in the code for reusing fragments, and that code calls those methods + // appropriately. + if self.spread.data().schema == *schema + && self.spread.data().type_condition_position == *parent_type + { + return Ok(Some(Selection::FragmentSpread(Arc::new(self.clone())))); + } + + // If we're rebasing on a _different_ schema, then we *must* have fragments, since reusing + // `self.fragments` would be incorrect. If we're on the same schema though, we're happy to default + // to `self.fragments`. + let rebase_on_same_schema = self.spread.data().schema == *schema; + let Some(named_fragment) = named_fragments.get(&self.spread.data().fragment_name) else { + // If we're rebasing on another schema (think a subgraph), then named fragments will have been rebased on that, and some + // of them may not contain anything that is on that subgraph, in which case they will not have been included at all. + // If so, then as long as we're not asked to error if we cannot rebase, then we're happy to skip that spread (since again, + // it expands to nothing that applies on the schema). + return if let RebaseErrorHandlingOption::ThrowError = error_handling { + Err(FederationError::internal(format!( + "Cannot rebase {} fragment if it isn't part of the provided fragments", + self.spread.data().fragment_name + ))) + } else { + Ok(None) + }; + }; + + // Lastly, if we rebase on a different schema, it's possible the fragment type does not intersect the + // parent type. For instance, the parent type could be some object type T while the fragment is an + // interface I, and T may implement I in the supergraph, but not in a particular subgraph (of course, + // if I doesn't exist at all in the subgraph, then we'll have exited above, but I may exist in the + // subgraph, just not be implemented by T for some reason). In that case, we can't reuse the fragment + // as its spread is essentially invalid in that position, so we have to replace it by the expansion + // of that fragment, which we rebase on the parentType (which in turn, will remove anythings within + // the fragment selection that needs removing, potentially everything). + if !rebase_on_same_schema + && !runtime_types_intersect( + parent_type, + &named_fragment.type_condition_position, + schema, + ) + { + // Note that we've used the rebased `named_fragment` to check the type intersection because we needed to + // compare runtime types "for the schema we're rebasing into". But now that we're deciding to not reuse + // this rebased fragment, what we rebase is the selection set of the non-rebased fragment. And that's + // important because the very logic we're hitting here may need to happen inside the rebase on the + // fragment selection, but that logic would not be triggered if we used the rebased `named_fragment` since + // `rebase_on_same_schema` would then be 'true'. + let expanded_selection_set = self.selection_set.rebase_on( + parent_type, + named_fragments, + schema, + error_handling, + )?; + // In theory, we could return the selection set directly, but making `SelectionSet.rebase_on` sometimes + // return a `SelectionSet` complicate things quite a bit. So instead, we encapsulate the selection set + // in an "empty" inline fragment. This make for non-really-optimal selection sets in the (relatively + // rare) case where this is triggered, but in practice this "inefficiency" is removed by future calls + // to `normalize`. + return if expanded_selection_set.selections.is_empty() { + Ok(None) + } else { + Ok(Some(Selection::from_inline_fragment( + InlineFragment::new(InlineFragmentData { + schema: schema.clone(), + parent_type_position: parent_type.clone(), + type_condition_position: None, + directives: Default::default(), + selection_id: SelectionId::new(), + }), + expanded_selection_set, + ))) + }; + } + + let spread = FragmentSpread::new(FragmentSpreadData::from_fragment( + &named_fragment, + &self.spread.data().directives, + )); + Ok(Some(Selection::FragmentSpread(Arc::new( + FragmentSpreadSelection { + spread, + selection_set: named_fragment.selection_set.clone(), + }, + )))) + } + + pub(crate) fn has_defer(&self) -> bool { + self.spread.data().directives.has("defer") || self.selection_set.has_defer() + } + + /// Copies fragment spread selection and assigns it a new unique selection ID. + pub(crate) fn with_unique_id(&self) -> Self { + let mut data = self.spread.data().clone(); + data.selection_id = SelectionId::new(); + Self { + spread: FragmentSpread::new(data), + selection_set: self.selection_set.clone(), + } + } + + /// Normalize this fragment spread into a "normalized" spread representation with following + /// modifications + /// - Stores the schema (may be useful for directives). + /// - Encloses list of directives in `Arc`s to facilitate cheaper cloning. + /// - Stores unique selection ID (used for deferred fragments) + pub(crate) fn from_fragment_spread( + fragment_spread: &executable::FragmentSpread, + fragment: &Node, + ) -> Result { + let spread_data = FragmentSpreadData::from_fragment(fragment, &fragment_spread.directives); + Ok(FragmentSpreadSelection { + spread: FragmentSpread::new(spread_data), + selection_set: fragment.selection_set.clone(), + }) + } + + pub(crate) fn normalize( + &self, + parent_type: &CompositeTypeDefinitionPosition, + named_fragments: &NamedFragments, + schema: &ValidFederationSchema, + ) -> Result, FederationError> { + let this_condition = self.spread.data().type_condition_position.clone(); + // This method assumes by contract that `parent_type` runtimes intersects `self.inline_fragment.data().parent_type_position`'s, + // but `parent_type` runtimes may be a subset. So first check if the selection should not be discarded on that account (that + // is, we should not keep the selection if its condition runtimes don't intersect at all with those of + // `parent_type` as that would ultimately make an invalid selection set). + if (self.spread.data().schema != *schema || this_condition != *parent_type) + && !runtime_types_intersect(&this_condition, parent_type, schema) + { + return Ok(None); + } + + // We must update the spread parent type if necessary since we're not going deeper, + // or we'll be fundamentally losing context. + if self.spread.data().schema != *schema { + return Err(FederationError::internal( + "Should not try to normalize using a type from another schema", + )); + } + + if let Some(rebased_fragment_spread) = self.rebase_on( + parent_type, + named_fragments, + schema, + RebaseErrorHandlingOption::ThrowError, + )? { + Ok(Some(SelectionOrSet::Selection(rebased_fragment_spread))) + } else { + unreachable!("We should always be able to either rebase the fragment spread OR throw an exception"); + } + } + + pub(crate) fn containment( + &self, + other: &Selection, + options: ContainmentOptions, + ) -> Containment { + match other { + // Using keys here means that @defer fragments never compare equal. + // This is a bit odd but it is consistent: the selection set data structure would not + // even try to compare two @defer fragments, because their keys are different. + Selection::FragmentSpread(other) if self.spread.key() == other.spread.key() => self + .selection_set + .containment(&other.selection_set, options), + _ => Containment::NotContained, + } + } + + /// Returns true if this selection is a superset of the other selection. + pub(crate) fn contains(&self, other: &Selection) -> bool { + self.containment(other, Default::default()).is_contained() + } +} + +impl FragmentSpreadData { + pub(crate) fn from_fragment( + fragment: &Node, + spread_directives: &executable::DirectiveList, + ) -> FragmentSpreadData { + FragmentSpreadData { + schema: fragment.schema.clone(), + fragment_name: fragment.name.clone(), + type_condition_position: fragment.type_condition_position.clone(), + directives: Arc::new(spread_directives.clone()), + fragment_directives: fragment.directives.clone(), + selection_id: SelectionId::new(), + } + } +} + +mod normalized_inline_fragment_selection { + use std::collections::HashSet; + use std::sync::Arc; + + use apollo_compiler::executable; + use apollo_compiler::executable::Name; + + use super::normalized_field_selection::collect_variables_from_directive; + use crate::error::FederationError; + use crate::link::graphql_definition::defer_directive_arguments; + use crate::link::graphql_definition::DeferDirectiveArguments; + use crate::query_plan::operation::directives_with_sorted_arguments; + use crate::query_plan::operation::is_deferred_selection; + use crate::query_plan::operation::runtime_types_intersect; + use crate::query_plan::operation::HasSelectionKey; + use crate::query_plan::operation::SelectionId; + use crate::query_plan::operation::SelectionKey; + use crate::query_plan::operation::SelectionSet; + use crate::query_plan::FetchDataPathElement; + use crate::schema::position::CompositeTypeDefinitionPosition; + use crate::schema::ValidFederationSchema; + + /// An analogue of the apollo-compiler type `InlineFragment` with these changes: + /// - Stores the inline fragment data (other than the selection set) in `InlineFragment`, + /// to facilitate operation paths and graph paths. + /// - For the type condition, stores the schema and the position in that schema instead of just + /// the `NamedType`. + /// - Stores the parent type explicitly, which means storing the position (in apollo-compiler, this + /// is in the parent selection set). + /// - Encloses collection types in `Arc`s to facilitate cheaper cloning. + #[derive(Debug, Clone, PartialEq, Eq)] + pub(crate) struct InlineFragmentSelection { + pub(crate) inline_fragment: InlineFragment, + pub(crate) selection_set: SelectionSet, + } + + impl InlineFragmentSelection { + pub(crate) fn with_updated_selection_set( + &self, + selection_set: Option, + ) -> Self { + Self { + inline_fragment: self.inline_fragment.clone(), + //FIXME + selection_set: selection_set.unwrap(), + } + } + + pub(crate) fn collect_variables<'selection>( + &'selection self, + variables: &mut HashSet<&'selection Name>, + ) -> Result<(), FederationError> { + self.inline_fragment.collect_variables(variables); + self.selection_set.collect_variables(variables) + } + } + + impl HasSelectionKey for InlineFragmentSelection { + fn key(&self) -> SelectionKey { + self.inline_fragment.key() + } + } + + /// The non-selection-set data of `InlineFragmentSelection`, used with operation paths and + /// graph paths. + #[derive(Clone, PartialEq, Eq, Hash)] + pub(crate) struct InlineFragment { + data: InlineFragmentData, + key: SelectionKey, + } + + impl std::fmt::Debug for InlineFragment { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.data.fmt(f) + } + } + + impl InlineFragment { + pub(crate) fn new(data: InlineFragmentData) -> Self { + Self { + key: data.key(), + data, + } + } + + pub(crate) fn schema(&self) -> &ValidFederationSchema { + &self.data.schema + } + + pub(crate) fn data(&self) -> &InlineFragmentData { + &self.data + } + + pub(crate) fn with_updated_type_condition( + &self, + new: Option, + ) -> Self { + let mut data = self.data().clone(); + data.type_condition_position = new; + Self::new(data) + } + pub(crate) fn with_updated_directives( + &self, + directives: executable::DirectiveList, + ) -> InlineFragment { + let mut data = self.data().clone(); + data.directives = Arc::new(directives); + Self::new(data) + } + + pub(crate) fn as_path_element(&self) -> Option { + let condition = self.data().type_condition_position.clone()?; + + Some(FetchDataPathElement::TypenameEquals( + condition.type_name().clone().into(), + )) + } + + pub(crate) fn collect_variables<'selection>( + &'selection self, + variables: &mut HashSet<&'selection Name>, + ) { + for dir in self.data.directives.iter() { + collect_variables_from_directive(dir, variables) + } + } + } + + impl HasSelectionKey for InlineFragment { + fn key(&self) -> SelectionKey { + self.key.clone() + } + } + + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + pub(crate) struct InlineFragmentData { + pub(crate) schema: ValidFederationSchema, + pub(crate) parent_type_position: CompositeTypeDefinitionPosition, + pub(crate) type_condition_position: Option, + pub(crate) directives: Arc, + pub(crate) selection_id: SelectionId, + } + + impl InlineFragmentData { + pub(crate) fn defer_directive_arguments( + &self, + ) -> Result, FederationError> { + if let Some(directive) = self.directives.get("defer") { + Ok(Some(defer_directive_arguments(directive)?)) + } else { + Ok(None) + } + } + + pub(super) fn casted_type_if_add_to( + &self, + parent_type: &CompositeTypeDefinitionPosition, + schema: &ValidFederationSchema, + ) -> Option { + if &self.parent_type_position == parent_type && &self.schema == schema { + return Some(self.casted_type()); + } + match self.can_rebase_on(parent_type) { + (false, _) => None, + (true, None) => Some(parent_type.clone()), + (true, Some(ty)) => Some(ty), + } + } + + pub(super) fn casted_type(&self) -> CompositeTypeDefinitionPosition { + self.type_condition_position + .clone() + .unwrap_or_else(|| self.parent_type_position.clone()) + } + + fn can_rebase_on( + &self, + parent_type: &CompositeTypeDefinitionPosition, + ) -> (bool, Option) { + let Some(ty) = self.type_condition_position.as_ref() else { + return (true, None); + }; + match self + .schema + .get_type(ty.type_name().clone()) + .and_then(CompositeTypeDefinitionPosition::try_from) + { + Ok(ty) if runtime_types_intersect(parent_type, &ty, &self.schema) => { + (true, Some(ty)) + } + _ => (false, None), + } + } + } + + impl HasSelectionKey for InlineFragmentData { + fn key(&self) -> SelectionKey { + if is_deferred_selection(&self.directives) { + SelectionKey::Defer { + deferred_id: self.selection_id.clone(), + } + } else { + SelectionKey::InlineFragment { + type_condition: self + .type_condition_position + .as_ref() + .map(|pos| pos.type_name().clone()), + directives: Arc::new(directives_with_sorted_arguments(&self.directives)), + } + } + } + } +} + +pub(crate) use normalized_inline_fragment_selection::InlineFragment; +pub(crate) use normalized_inline_fragment_selection::InlineFragmentData; +pub(crate) use normalized_inline_fragment_selection::InlineFragmentSelection; + +use crate::schema::position::INTROSPECTION_TYPENAME_FIELD_NAME; + +// TODO(@goto-bus-stop): merge this with the other Operation impl block. +impl Operation { + pub(crate) fn optimize( + &mut self, + fragments: Option<&NamedFragments>, + min_usages_to_optimize: Option, + ) { + let min_usages_to_optimize = min_usages_to_optimize.unwrap_or(2); + let Some(fragments) = fragments else { return }; + if fragments.is_empty() { + return; + } + assert!( + min_usages_to_optimize >= 1, + "Expected 'min_usages_to_optimize' to be at least 1, but got {min_usages_to_optimize}" + ); + + todo!(); // TODO: port JS `Operation.optimize` from `operations.ts` + } +} + +/// A simple MultiMap implementation using IndexMap with Vec as its value type. +/// - Preserves the insertion order of keys and values. +struct MultiIndexMap(IndexMap>); + +impl Deref for MultiIndexMap { + type Target = IndexMap>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl MultiIndexMap +where + K: Eq + Hash, +{ + fn new() -> Self { + Self(IndexMap::new()) + } + + fn insert(&mut self, key: K, value: V) { + self.0.entry(key).or_default().push(value); + } + + fn extend>(&mut self, iterable: I) { + for (key, value) in iterable { + self.insert(key, value); + } + } +} + +/// the return type of `lazy_map` function's `mapper` closure argument +#[derive(derive_more::From)] +enum SelectionMapperReturn { + None, + Selection(Selection), + SelectionList(Vec), +} + +impl FromIterator for SelectionMapperReturn { + fn from_iter(iter: T) -> Self + where + T: IntoIterator, + { + Self::SelectionList(Vec::from_iter(iter)) + } +} + +impl SelectionSet { + pub(crate) fn empty( + schema: ValidFederationSchema, + type_position: CompositeTypeDefinitionPosition, + ) -> Self { + Self { + schema, + type_position, + selections: Default::default(), + } + } + + /// PORT_NOTE: JS calls this `newCompositeTypeSelectionSet` + pub(crate) fn for_composite_type( + schema: ValidFederationSchema, + type_position: CompositeTypeDefinitionPosition, + ) -> Self { + let typename_field = Field::new_introspection_typename(&schema, &type_position, None) + .with_subselection(None); + Self { + schema, + type_position, + selections: Arc::new(std::iter::once(typename_field).collect()), + } + } + + /// Build a selection set from a single selection. + pub(crate) fn from_selection( + type_position: CompositeTypeDefinitionPosition, + selection: Selection, + ) -> Self { + let schema = selection.schema().clone(); + let mut selection_map = SelectionMap::new(); + selection_map.insert(selection); + Self { + schema, + type_position, + selections: Arc::new(selection_map), + } + } + + /// Build a selection set from the given selections. This does **not** handle merging of + /// selections with the same keys! + pub(crate) fn from_raw_selections>( + schema: ValidFederationSchema, + type_position: CompositeTypeDefinitionPosition, + selections: impl IntoIterator, + ) -> Self { + Self { + schema, + type_position, + selections: Arc::new(selections.into_iter().collect()), + } + } + + #[cfg(any(doc, test))] + pub fn parse( + schema: ValidFederationSchema, + type_position: CompositeTypeDefinitionPosition, + source_text: &str, + ) -> Result { + let selection_set = crate::schema::field_set::parse_field_set_without_normalization( + schema.schema(), + type_position.type_name().clone(), + source_text, + )?; + let named_fragments = NamedFragments::new(&IndexMap::new(), &schema); + SelectionSet::from_selection_set(&selection_set, &named_fragments, &schema) + } + + fn is_empty(&self) -> bool { + self.selections.is_empty() + } + + pub(crate) fn contains_top_level_field(&self, field: &Field) -> Result { + if let Some(selection) = self.selections.get(&field.key()) { + let Selection::Field(field_selection) = selection else { + return Err(Internal { + message: format!( + "Field selection key for field \"{}\" references non-field selection", + field.data().field_position, + ), + } + .into()); + }; + Ok(field_selection.field == *field) + } else { + Ok(false) + } + } + + /// Normalize this selection set (merging selections with the same keys), with the following + /// additional transformations: + /// - Expand fragment spreads into inline fragments. + /// - Remove `__schema` or `__type` introspection fields, as these shouldn't be handled by query + /// planning. + /// - Hoist fragment spreads/inline fragments into their parents if they have no directives and + /// their parent type matches. + /// + /// Note this function asserts that the type of the selection set is a composite type (i.e. this + /// isn't the empty selection set of some leaf field), and will return error if this is not the + /// case. + pub(crate) fn from_selection_set( + selection_set: &executable::SelectionSet, + fragments: &NamedFragments, + schema: &ValidFederationSchema, + ) -> Result { + let type_position: CompositeTypeDefinitionPosition = + schema.get_type(selection_set.ty.clone())?.try_into()?; + let mut normalized_selections = vec![]; + SelectionSet::normalize_selections( + &selection_set.selections, + &type_position, + &mut normalized_selections, + fragments, + schema, + )?; + let mut merged = SelectionSet { + schema: schema.clone(), + type_position, + selections: Arc::new(SelectionMap::new()), + }; + merged.merge_selections_into(normalized_selections.iter())?; + Ok(merged) + } + + /// A helper function for normalizing a list of selections into a destination. + fn normalize_selections( + selections: &[executable::Selection], + parent_type_position: &CompositeTypeDefinitionPosition, + destination: &mut Vec, + fragments: &NamedFragments, + schema: &ValidFederationSchema, + ) -> Result<(), FederationError> { + for selection in selections { + match selection { + executable::Selection::Field(field_selection) => { + let Some(normalized_field_selection) = FieldSelection::from_field( + field_selection, + parent_type_position, + fragments, + schema, + )? + else { + continue; + }; + destination.push(Selection::from(normalized_field_selection)); + } + executable::Selection::FragmentSpread(fragment_spread_selection) => { + let Some(fragment) = fragments.get(&fragment_spread_selection.fragment_name) + else { + return Err(Internal { + message: format!( + "Fragment spread referenced non-existent fragment \"{}\"", + fragment_spread_selection.fragment_name, + ), + } + .into()); + }; + // if we don't expand fragments, we need to normalize it + let normalized_fragment_spread = FragmentSpreadSelection::from_fragment_spread( + fragment_spread_selection, + &fragment, + )?; + destination.push(Selection::FragmentSpread(Arc::new( + normalized_fragment_spread, + ))); + } + executable::Selection::InlineFragment(inline_fragment_selection) => { + let is_on_parent_type = + if let Some(type_condition) = &inline_fragment_selection.type_condition { + type_condition == parent_type_position.type_name() + } else { + true + }; + // We can hoist/collapse inline fragments if their type condition is on the + // parent type (or they have no type condition) and they don't have any + // directives. + // + // PORT_NOTE: The JS codebase didn't hoist inline fragments, only fragment + // spreads (presumably because named fragments would commonly be on the same + // type as their fragment spread usages). It should be fine to also hoist inline + // fragments though if we notice they're similarly useless (and presumably later + // transformations in the JS codebase would take care of this). + if is_on_parent_type && inline_fragment_selection.directives.is_empty() { + SelectionSet::normalize_selections( + &inline_fragment_selection.selection_set.selections, + parent_type_position, + destination, + fragments, + schema, + )?; + } else { + let normalized_inline_fragment_selection = + InlineFragmentSelection::from_inline_fragment( + inline_fragment_selection, + parent_type_position, + fragments, + schema, + )?; + destination.push(Selection::InlineFragment(Arc::new( + normalized_inline_fragment_selection, + ))); + } + } + } + } + Ok(()) + } + + /// Merges the given normalized selection sets into this one. + pub(crate) fn merge_into<'op>( + &mut self, + others: impl Iterator, + ) -> Result<(), FederationError> { + let mut selections_to_merge = vec![]; + for other in others { + if other.schema != self.schema { + return Err(FederationError::internal( + "Cannot merge selection sets from different schemas", + )); + } + if other.type_position != self.type_position { + return Err(FederationError::internal( + format!( + "Cannot merge selection set for type \"{}\" into a selection set for type \"{}\"", + other.type_position, + self.type_position, + ), + )); + } + selections_to_merge.extend(other.selections.values()); + } + self.merge_selections_into(selections_to_merge.into_iter()) + } + + /// A helper function for merging the given selections into this one. + fn merge_selections_into<'op>( + &mut self, + others: impl Iterator, + ) -> Result<(), FederationError> { + let mut fields = IndexMap::new(); + let mut fragment_spreads = IndexMap::new(); + let mut inline_fragments = IndexMap::new(); + let target = Arc::make_mut(&mut self.selections); + for other_selection in others { + let other_key = other_selection.key(); + match target.entry(other_key.clone()) { + normalized_selection_map::Entry::Occupied(existing) => match existing.get() { + Selection::Field(self_field_selection) => { + let Selection::Field(other_field_selection) = other_selection else { + return Err(Internal { + message: format!( + "Field selection key for field \"{}\" references non-field selection", + self_field_selection.field.data().field_position, + ), + }.into()); + }; + fields + .entry(other_key) + .or_insert_with(Vec::new) + .push(other_field_selection); + } + Selection::FragmentSpread(self_fragment_spread_selection) => { + let Selection::FragmentSpread(other_fragment_spread_selection) = + other_selection + else { + return Err(Internal { + message: format!( + "Fragment spread selection key for fragment \"{}\" references non-field selection", + self_fragment_spread_selection.spread.data().fragment_name, + ), + }.into()); + }; + fragment_spreads + .entry(other_key) + .or_insert_with(Vec::new) + .push(other_fragment_spread_selection); + } + Selection::InlineFragment(self_inline_fragment_selection) => { + let Selection::InlineFragment(other_inline_fragment_selection) = + other_selection + else { + return Err(Internal { + message: format!( + "Inline fragment selection key under parent type \"{}\" {}references non-field selection", + self_inline_fragment_selection.inline_fragment.data().parent_type_position, + self_inline_fragment_selection.inline_fragment.data().type_condition_position.clone() + .map_or_else( + String::new, + |cond| format!("(type condition: {}) ", cond), + ), + ), + }.into()); + }; + inline_fragments + .entry(other_key) + .or_insert_with(Vec::new) + .push(other_inline_fragment_selection); + } + }, + normalized_selection_map::Entry::Vacant(vacant) => { + vacant.insert(other_selection.clone())?; + } + } + } + + for (key, self_selection) in target.iter_mut() { + match self_selection { + SelectionValue::Field(mut self_field_selection) => { + if let Some(other_field_selections) = fields.shift_remove(key) { + self_field_selection.merge_into( + other_field_selections.iter().map(|selection| &***selection), + )?; + } + } + SelectionValue::FragmentSpread(mut self_fragment_spread_selection) => { + if let Some(other_fragment_spread_selections) = + fragment_spreads.shift_remove(key) + { + self_fragment_spread_selection.merge_into( + other_fragment_spread_selections + .iter() + .map(|selection| &***selection), + )?; + } + } + SelectionValue::InlineFragment(mut self_inline_fragment_selection) => { + if let Some(other_inline_fragment_selections) = + inline_fragments.shift_remove(key) + { + self_inline_fragment_selection.merge_into( + other_inline_fragment_selections + .iter() + .map(|selection| &***selection), + )?; + } + } + } + } + + Ok(()) + } + + pub(crate) fn expand_all_fragments(&self) -> Result { + let mut expanded_selections = vec![]; + SelectionSet::expand_selection_set(&mut expanded_selections, self)?; + + let mut expanded = SelectionSet { + schema: self.schema.clone(), + type_position: self.type_position.clone(), + selections: Arc::new(SelectionMap::new()), + }; + expanded.merge_selections_into(expanded_selections.iter())?; + Ok(expanded) + } + + fn expand_selection_set( + destination: &mut Vec, + selection_set: &SelectionSet, + ) -> Result<(), FederationError> { + for (_, value) in selection_set.selections.iter() { + match value { + Selection::Field(field_selection) => { + let selections = match &field_selection.selection_set { + Some(s) => Some(s.expand_all_fragments()?), + None => None, + }; + destination.push(Selection::from_field( + field_selection.field.clone(), + selections, + )) + } + Selection::FragmentSpread(spread_selection) => { + let fragment_spread_data = spread_selection.spread.data(); + // We can hoist/collapse named fragments if their type condition is on the + // parent type and they don't have any directives. + if fragment_spread_data.type_condition_position == selection_set.type_position + && fragment_spread_data.directives.is_empty() + { + SelectionSet::expand_selection_set( + destination, + &spread_selection.selection_set, + )?; + } else { + // convert to inline fragment + let expanded = InlineFragmentSelection::from_fragment_spread_selection( + spread_selection, + )?; + destination.push(Selection::InlineFragment(Arc::new(expanded))); + } + } + Selection::InlineFragment(inline_selection) => { + destination.push(Selection::from_inline_fragment( + inline_selection.inline_fragment.clone(), + inline_selection.selection_set.expand_all_fragments()?, + )); + } + } + } + Ok(()) + } + + /// Modifies the provided selection set to optimize the handling of __typename selections for query planning. + /// + /// __typename information can always be provided by any subgraph declaring that type. While this data can be + /// theoretically fetched from multiple sources, in practice it doesn't really matter which subgraph we use + /// for the __typename and we should just get it from the same source as the one that was used to resolve + /// other fields. + /// + /// In most cases, selecting __typename won't be a problem as query planning algorithm ignores "obviously" + /// inefficient paths. Typically, querying the __typename of an entity is generally ok because when looking at + /// a path, the query planning algorithm always favor getting a field "locally" if it can (which it always can + /// for __typename) and ignore alternative that would jump subgraphs. + /// + /// When querying a __typename after a @shareable field, query planning algorithm would consider getting the + /// __typename from EACH version of the @shareable field. This unnecessarily explodes the number of possible + /// query plans with some useless options and results in degraded performance. Since the number of possible + /// plans doubles for every field for which there is a choice, eliminating unnecessary choices improves query + /// planning performance. + /// + /// It is unclear how to do this cleanly with the current planning algorithm, so this method is a workaround + /// so we can efficiently generate query plans. In order to prevent the query planner from spending time + /// exploring those useless __typename options, we "remove" the unnecessary __typename selections from the + /// operation. Since we need to ensure that the __typename field will still need to be queried, we "tag" + /// one of the "sibling" selections (using "attachement") to remember that __typename needs to be added + /// back eventually. The core query planning algorithm will ignore that tag, and because __typename has been + /// otherwise removed, we'll save any related work. As we build the final query plan, we'll check back for + /// those "tags" and add back the __typename selections. As this only happen after the query planning + /// algorithm has computed all choices, we achieve our goal of not considering useless choices due to + /// __typename. Do note that if __typename is the "only" selection of some selection set, then we leave it + /// untouched, and let the query planning algorithm treat it as any other field. We have no other choice in + /// that case, and that's actually what we want. + pub(crate) fn optimize_sibling_typenames( + &mut self, + interface_types_with_interface_objects: &IndexSet, + ) -> Result<(), FederationError> { + let is_interface_object = + interface_types_with_interface_objects.contains(&InterfaceTypeDefinitionPosition { + type_name: self.type_position.type_name().clone(), + }); + let mut typename_field_key: Option = None; + let mut sibling_field_key: Option = None; + + let mutable_selection_map = Arc::make_mut(&mut self.selections); + for (key, entry) in mutable_selection_map.iter_mut() { + match entry { + SelectionValue::Field(mut field_selection) => { + if field_selection.get().field.data().name() == &TYPENAME_FIELD + && !is_interface_object + && typename_field_key.is_none() + { + typename_field_key = Some(key.clone()); + } else if sibling_field_key.is_none() { + sibling_field_key = Some(key.clone()); + } + + if let Some(field_selection_set) = field_selection.get_selection_set_mut() { + field_selection_set + .optimize_sibling_typenames(interface_types_with_interface_objects)?; + } + } + SelectionValue::InlineFragment(mut inline_fragment) => { + inline_fragment + .get_selection_set_mut() + .optimize_sibling_typenames(interface_types_with_interface_objects)?; + } + SelectionValue::FragmentSpread(fragment_spread) => { + // at this point in time all fragment spreads should have been converted into inline fragments + return Err(FederationError::SingleFederationError(Internal { + message: format!( + "Error while optimizing sibling typename information, selection set contains {} named fragment", + fragment_spread.get().spread.data().fragment_name + ), + })); + } + } + } + + if let (Some(typename_key), Some(sibling_field_key)) = + (typename_field_key, sibling_field_key) + { + if let ( + Some((_, Selection::Field(typename_field))), + Some(SelectionValue::Field(mut sibling_field)), + ) = ( + mutable_selection_map.remove(&typename_key), + mutable_selection_map.get_mut(&sibling_field_key), + ) { + *sibling_field.get_sibling_typename_mut() = + Some(typename_field.field.data().response_name()); + } else { + unreachable!("typename and sibling fields must both exist at this point") + } + } + Ok(()) + } + + pub(crate) fn without_empty_branches(&self) -> Result>, FederationError> { + let filtered = self.filter_recursive_depth_first(&mut |sel| match sel { + Selection::Field(field) => Ok(if let Some(set) = &field.selection_set { + !set.is_empty() + } else { + true + }), + Selection::InlineFragment(inline) => Ok(!inline.selection_set.is_empty()), + Selection::FragmentSpread(_) => { + Err(FederationError::internal("unexpected fragment spread")) + } + })?; + Ok(if filtered.selections.is_empty() { + None + } else { + Some(filtered) + }) + } + + pub(crate) fn filter_recursive_depth_first( + &self, + predicate: &mut dyn FnMut(&Selection) -> Result, + ) -> Result, FederationError> { + match self.selections.filter_recursive_depth_first(predicate)? { + Cow::Borrowed(_) => Ok(Cow::Borrowed(self)), + Cow::Owned(selections) => Ok(Cow::Owned(Self { + schema: self.schema.clone(), + type_position: self.type_position.clone(), + selections: Arc::new(selections), + })), + } + } + + pub(crate) fn conditions(&self) -> Result { + // If the conditions of all the selections within the set are the same, + // then those are conditions of the whole set and we return it. + // Otherwise, we just return `true` + // (which essentially translate to "that selection always need to be queried"). + // Note that for the case where the set has only 1 selection, + // then this just mean we return the condition of that one selection. + // Also note that in theory we could be a tad more precise, + // and when all the selections have variable conditions, + // we could return the intersection of all of them, + // but we don't bother for now as that has probably extremely rarely an impact in practice. + let mut selections = self.selections.values(); + let Some(first_selection) = selections.next() else { + // we shouldn't really get here for well-formed selection, so whether we return true or false doesn't matter + // too much, but in principle, if there is no selection, we should be cool not including it. + return Ok(Conditions::Boolean(false)); + }; + let conditions = first_selection.conditions()?; + for selection in selections { + if selection.conditions()? != conditions { + return Ok(Conditions::Boolean(true)); + } + } + Ok(conditions) + } + + /// Build a selection by merging all items in the given selections (slice). + /// - Assumes all items in the slice have the same selection key. + fn make_selection<'a>( + schema: &ValidFederationSchema, + parent_type: &CompositeTypeDefinitionPosition, + selections: impl Iterator, + ) -> Result { + let mut iter = selections; + let Some(first) = iter.next() else { + // PORT_NOTE: The TypeScript version asserts here. + return Err(FederationError::internal( + "Should not be called without any updates", + )); + }; + let Some(second) = iter.next() else { + // Optimize for the simple case of a single selection, as we don't have to do anything + // complex to merge the sub-selections. + return first + .rebase_on( + parent_type, + /*named_fragments*/ &Default::default(), + schema, + RebaseErrorHandlingOption::ThrowError, + )? + .ok_or_else(|| FederationError::internal("Unable to rebase selection updates")); + }; + + let element = first.element()?.rebase_on( + parent_type, + schema, + RebaseErrorHandlingOption::ThrowError, + )?; + let Some(element) = element else { + return Err(FederationError::internal( + "Unable to rebase selection updates", + )); + }; + let sub_selection_parent_type: Option = match element { + OpPathElement::Field(ref field) => field.data().output_base_type()?.try_into().ok(), + OpPathElement::InlineFragment(ref inline) => Some(inline.data().casted_type()), + }; + + let Some(ref sub_selection_parent_type) = sub_selection_parent_type else { + // This is a leaf, so all updates should correspond ot the same field and we just use the first. + return Selection::from_element(element, /*sub_selection*/ None); + }; + + // This case has a sub-selection. Merge all sub-selection updates. + let mut sub_selection_updates: MultiIndexMap = + MultiIndexMap::new(); + for selection in [first, second].into_iter().chain(iter) { + if let Some(sub_selection_set) = selection.selection_set()? { + sub_selection_updates.extend( + sub_selection_set + .selections + .iter() + .map(|(k, v)| (k.clone(), v.clone())), + ); + } + } + let updated_sub_selection = Some(Self::make_selection_set( + schema, + sub_selection_parent_type, + sub_selection_updates.values().map(|v| v.iter()), + )?); + Selection::from_element(element, updated_sub_selection) + } + + /// Build a selection set by aggregating all items from the `selection_key_groups` iterator. + /// - Assumes each item (slice) from the iterator has the same selection key within the slice. + /// - Note that if the same selection key repeats in a later group, the previous group will be + /// ignored and replaced by the new group. + fn make_selection_set<'a>( + schema: &ValidFederationSchema, + parent_type: &CompositeTypeDefinitionPosition, + selection_key_groups: impl Iterator>, + ) -> Result { + let mut result = SelectionMap::new(); + for group in selection_key_groups { + let selection = Self::make_selection(schema, parent_type, group)?; + result.insert(selection); + } + Ok(SelectionSet { + schema: schema.clone(), + type_position: parent_type.clone(), + selections: Arc::new(result), + }) + } + + // PORT_NOTE: Some features of the TypeScript `lazyMap` were not ported: + // - `parentType` (optional) parameter: This is only used in `SelectionSet.normalize` method, + // but its Rust version doesn't use `lazy_map`. + // - `mapper` may return a `SelectionSet`. + // For simplicity, this case was not ported. It was used by `normalize` method in the TypeScript. + // But, the Rust version doesn't use `lazy_map`. + // - `mapper` may return `PathBasedUpdate`. + // The `PathBasedUpdate` case is only used in `withFieldAliased` function in the TypeScript + // version, but its Rust version doesn't use `lazy_map`. + // PORT_NOTE #2: Taking ownership of `self` in this method was considered. However, calling + // `Arc::make_mut` on the `Arc` fields of `self` didn't seem better than cloning Arc instances. + fn lazy_map( + &self, + mut mapper: impl FnMut(&Selection) -> Result, + ) -> Result { + let mut iter = self.selections.values(); + + // Find the first object that is not identical after mapping + let Some((index, (_, first_changed))) = iter + .by_ref() + .map(|sel| (sel, mapper(sel))) + .enumerate() + .find(|(_, (sel, updated))| + !matches!(&updated, Ok(SelectionMapperReturn::Selection(updated)) if updated == *sel)) + else { + // All selections are identical after mapping, so just clone `self`. + return Ok(self.clone()); + }; + + // The mapped selection could be an error, so we need to not forget about it. + let first_changed = first_changed?; + // Copy the first half of the selections until the `index`-th item, since they are not + // changed. + let mut updated_selections = MultiIndexMap::new(); + updated_selections.extend( + self.selections + .iter() + .take(index) + .map(|(k, v)| (k.clone(), v.clone())), + ); + + let mut update_new_selection = |selection| match selection { + SelectionMapperReturn::None => {} // Removed; Skip it. + SelectionMapperReturn::Selection(new_selection) => { + updated_selections.insert(new_selection.key(), new_selection) + } + SelectionMapperReturn::SelectionList(new_selections) => { + updated_selections.extend(new_selections.into_iter().map(|s| (s.key(), s))) + } + }; + + // Now update the rest of the selections using the `mapper` function. + update_new_selection(first_changed); + for selection in iter { + update_new_selection(mapper(selection)?) + } + + Self::make_selection_set( + &self.schema, + &self.type_position, + updated_selections.values().map(|v| v.iter()), + ) + } + + pub(crate) fn add_back_typename_in_attachments(&self) -> Result { + self.lazy_map(|selection| { + let selection_element = selection.element()?; + let updated = selection + .map_selection_set(|ss| ss.add_back_typename_in_attachments().map(Some))?; + let Some(sibling_typename) = selection_element.sibling_typename() else { + // No sibling typename to add back + return Ok(updated.into()); + }; + // We need to add the query __typename for the current type in the current group. + // Note that the value of the sibling_typename is the alias or "" if there is no alias + let alias = if sibling_typename.is_empty() { + None + } else { + Some(sibling_typename.clone()) + }; + let field_element = Field::new_introspection_typename( + &self.schema, + &selection.element()?.parent_type_position(), + alias, + ); + let typename_selection = + Selection::from_element(field_element.into(), /*subselection*/ None)?; + Ok([typename_selection, updated].into_iter().collect()) + }) + } + + pub(crate) fn add_typename_field_for_abstract_types( + &self, + parent_type_if_abstract: Option, + fragments: &Option<&mut RebasedFragments>, + ) -> Result { + let mut selection_map = SelectionMap::new(); + if let Some(parent) = parent_type_if_abstract { + if !self.has_top_level_typename_field() { + let typename_selection = Selection::from_field( + Field::new_introspection_typename(&self.schema, &parent.into(), None), + None, + ); + selection_map.insert(typename_selection); + } + } + for selection in self.selections.values() { + selection_map.insert(if let Some(selection_set) = selection.selection_set()? { + let type_if_abstract = + subselection_type_if_abstract(selection, &self.schema, fragments); + let updated_selection_set = selection_set + .add_typename_field_for_abstract_types(type_if_abstract, fragments)?; + + if updated_selection_set == *selection_set { + selection.clone() + } else { + selection.with_updated_selection_set(Some(updated_selection_set))? + } + } else { + selection.clone() + }); + } + + Ok(SelectionSet { + schema: self.schema.clone(), + type_position: self.type_position.clone(), + selections: Arc::new(selection_map), + }) + } + + fn has_top_level_typename_field(&self) -> bool { + // Needs to be behind a OnceLock because `Arc::new` is non-const. + // XXX(@goto-bus-stop): Note this does *not* count `__typename @include(if: true)`. + // This seems wrong? But it's what JS does, too. + static TYPENAME_KEY: OnceLock = OnceLock::new(); + let key = TYPENAME_KEY.get_or_init(|| SelectionKey::Field { + response_name: TYPENAME_FIELD, + directives: Arc::new(Default::default()), + }); + + self.selections.contains_key(key) + } + + /// Inserts a `Selection` into the inner map. Should a selection with the same key already + /// exist in the map, the existing selection and the given selection are merged, replacing the + /// existing selection while keeping the same insertion index. + fn add_selection( + &mut self, + parent_type: &CompositeTypeDefinitionPosition, + schema: &ValidFederationSchema, + selection: Selection, + ) -> Result<(), FederationError> { + let selections = Arc::make_mut(&mut self.selections); + + let key = selection.key(); + match selections.remove(&key) { + Some((index, existing_selection)) => { + let to_merge = [existing_selection, selection]; + // `existing_selection` and `selection` both have the same selection key, + // so the merged selection will also have the same selection key. + let selection = SelectionSet::make_selection(schema, parent_type, to_merge.iter())?; + selections.insert_at(index, selection); + } + None => { + selections.insert(selection); + } + } + + Ok(()) + } + + /// Adds a path, and optional some selections following that path, to this selection map. + /// + /// Today, it is possible here to add conflicting paths, such as: + /// - `add_at_path("field1(arg: 1)")` + /// - `add_at_path("field1(arg: 2)")` + /// + /// Users of this method should guarantee that this doesn't happen. Otherwise, converting this + /// SelectionSet back to an ExecutableDocument will return a validation error. + /// + /// The final selections are optional. If `path` ends on a leaf field, then no followup + /// selections would make sense. + /// When final selections are provided, unecessary fragments will be automatically removed + /// at the junction between the path and those final selections. + /// + /// For instance, suppose that we have: + /// - a `path` argument that is `a::b::c`, + /// where the type of the last field `c` is some object type `C`. + /// - a `selections` argument that is `{ ... on C { d } }`. + /// + /// Then the resulting built selection set will be: `{ a { b { c { d } } }`, + /// and in particular the `... on C` fragment will be eliminated since it is unecesasry + /// (since again, `c` is of type `C`). + pub(crate) fn add_at_path( + &mut self, + path: &[Arc], + selection_set: Option<&Arc>, + ) -> Result<(), FederationError> { + // PORT_NOTE: This method was ported from the JS class `SelectionSetUpdates`. Unlike the + // JS code, this mutates the selection set map in-place. + match path.split_first() { + // If we have a sub-path, recurse. + Some((ele, path @ &[_, ..])) => { + let mut selection = Arc::make_mut(&mut self.selections) + .entry(ele.key()) + .or_insert(|| { + Selection::from_element( + OpPathElement::clone(ele), + // We immediately add a selection afterward to make this selection set + // valid. + Some(SelectionSet::empty( + self.schema.clone(), + self.type_position.clone(), + )), + ) + })?; + match &mut selection { + SelectionValue::Field(field) => match field.get_selection_set_mut() { + Some(sub_selection) => sub_selection.add_at_path(path, selection_set)?, + None => return Err(FederationError::internal("add_at_path encountered a field without a subselection which should never happen".to_string())), + }, + SelectionValue::InlineFragment(fragment) => fragment + .get_selection_set_mut() + .add_at_path(path, selection_set)?, + SelectionValue::FragmentSpread(_fragment) => { + return Err(FederationError::internal("add_at_path encountered a named fragment spread which should never happen".to_string())); + } + }; + } + // If we have no sub-path, we can add the selection. + Some((ele, &[])) => { + // PORT_NOTE: The JS code waited until the final selection was being constructed to + // turn the path and selection set into a selection. Because we are mutating things + // in-place, we eagerly construct the selection. + let element = OpPathElement::clone(ele); + let selection = Selection::from_element( + element, + selection_set.map(|set| SelectionSet::clone(set)), + )?; + self.add_selection(&ele.parent_type_position(), ele.schema(), selection)? + } + // If we don't have any path, we merge in the given subselections at the root. + None => { + if let Some(sel) = selection_set { + let parent_type = &sel.type_position; + let schema = sel.schema.clone(); + sel.selections + .values() + .cloned() + .try_for_each(|sel| self.add_selection(parent_type, &schema, sel))?; + } + } + } + Ok(()) + } + + fn collect_used_fragment_names(&self, aggregator: &mut HashMap) { + self.selections + .iter() + .for_each(|(_, s)| s.collect_used_fragment_names(aggregator)); + } + + pub(crate) fn rebase_on( + &self, + parent_type: &CompositeTypeDefinitionPosition, + named_fragments: &NamedFragments, + schema: &ValidFederationSchema, + error_handling: RebaseErrorHandlingOption, + ) -> Result { + let rebased_results = self + .selections + .iter() + .filter_map(|(_, selection)| { + selection + .rebase_on(parent_type, named_fragments, schema, error_handling) + .transpose() + }) + .collect::, _>>()?; + Ok(SelectionSet::from_raw_selections( + schema.clone(), + parent_type.clone(), + rebased_results, + )) + } + + /// Applies some normalization rules to this selection set in the context of the provided `parent_type`. + /// + /// Normalization mostly removes unnecessary/redundant inline fragments, so that for instance, with a schema: + /// ```graphql + /// type Query { + /// t1: T1 + /// i: I + /// } + /// + /// interface I { + /// id: ID! + /// } + /// + /// type T1 implements I { + /// id: ID! + /// v1: Int + /// } + /// + /// type T2 implements I { + /// id: ID! + /// v2: Int + /// } + /// ``` + /// We can perform following normalization + /// ```graphql + /// normalize({ + /// t1 { + /// ... on I { + /// id + /// } + /// } + /// i { + /// ... on T1 { + /// ... on I { + /// ... on T1 { + /// v1 + /// } + /// ... on T2 { + /// v2 + /// } + /// } + /// } + /// ... on T2 { + /// ... on I { + /// id + /// } + /// } + /// } + /// }) === { + /// t1 { + /// id + /// } + /// i { + /// ... on T1 { + /// v1 + /// } + /// ... on T2 { + /// id + /// } + /// } + /// } + /// ``` + /// + /// For this operation to be valid (to not throw), `parent_type` must be such that every field selection in + /// this selection set is such that its type position intersects with passed `parent_type` (there is no limitation + /// on the fragment selections, though any fragment selections whose condition do not intersects `parent_type` + /// will be discarded). Note that `self.normalize(self.type_condition)` is always valid and useful, but it is + /// also possible to pass a `parent_type` that is more "restrictive" than the selection current type position + /// (as long as the top-level fields of this selection set can be rebased on that type). + /// + /// Passing the option `recursive == false` makes the normalization only apply at the top-level, removing + /// any unnecessary top-level inline fragments, possibly multiple layers of them, but we never recurse + /// inside the sub-selection of an selection that is not removed by the normalization. + pub(crate) fn normalize( + &self, + parent_type: &CompositeTypeDefinitionPosition, + named_fragments: &NamedFragments, + schema: &ValidFederationSchema, + option: NormalizeSelectionOption, + ) -> Result { + let mut normalized_selection_map = SelectionMap::new(); + for (_, selection) in self.selections.iter() { + if let Some(selection_or_set) = + selection.normalize(parent_type, named_fragments, schema, option)? + { + match selection_or_set { + SelectionOrSet::Selection(normalized_selection) => { + normalized_selection_map.insert(normalized_selection); + } + SelectionOrSet::SelectionSet(normalized_set) => { + normalized_selection_map.extend_ref(&normalized_set.selections); + } + } + } + } + + Ok(SelectionSet { + schema: self.schema.clone(), + type_position: self.type_position.clone(), + selections: Arc::new(normalized_selection_map), + }) + } + + pub(crate) fn can_rebase_on(&self, parent_type: &CompositeTypeDefinitionPosition) -> bool { + self.selections + .values() + .all(|sel| sel.can_add_to(parent_type, &self.schema)) + } + + fn has_defer(&self) -> bool { + self.selections.values().any(|s| s.has_defer()) + } + + pub(crate) fn add_aliases_for_non_merging_fields( + &self, + ) -> Result<(SelectionSet, Vec>), FederationError> { + let mut aliases = Vec::new(); + compute_aliases_for_non_merging_fields( + vec![SelectionSetAtPath { + path: Vec::new(), + selections: Some(self.clone()), + }], + &mut aliases, + &self.schema, + )?; + + let updated = self.with_field_aliased(&aliases)?; + let output_rewrites = aliases + .into_iter() + .map( + |FieldToAlias { + mut path, + response_name, + alias, + }| { + path.push(FetchDataPathElement::Key(alias.into())); + Arc::new(FetchDataRewrite::KeyRenamer(FetchDataKeyRenamer { + path, + rename_key_to: response_name, + })) + }, + ) + .collect::>(); + + Ok((updated, output_rewrites)) + } + + pub(crate) fn with_field_aliased( + &self, + aliases: &[FieldToAlias], + ) -> Result { + if aliases.is_empty() { + return Ok(self.clone()); + } + + let mut at_current_level: HashMap = HashMap::new(); + let mut remaining: Vec<&FieldToAlias> = Vec::new(); + + for alias in aliases { + if !alias.path.is_empty() { + remaining.push(alias); + } else { + at_current_level.insert( + FetchDataPathElement::Key(alias.response_name.clone()), + alias, + ); + } + } + + let mut selection_map = SelectionMap::new(); + for selection in self.selections.values() { + let path_element = selection.element()?.as_path_element(); + let subselection_aliases = remaining + .iter() + .filter_map(|alias| { + if alias.path.first() == path_element.as_ref() { + Some(FieldToAlias { + path: alias.path[1..].to_vec(), + response_name: alias.response_name.clone(), + alias: alias.alias.clone(), + }) + } else { + None + } + }) + .collect::>(); + let selection_set = selection.selection_set()?; + let updated_selection_set = selection_set + .map(|selection_set| selection_set.with_field_aliased(&subselection_aliases)) + .transpose()?; + + match selection { + Selection::Field(field) => { + let alias = path_element.and_then(|elem| at_current_level.get(&elem)); + if alias.is_none() && selection_set == updated_selection_set.as_ref() { + selection_map.insert(selection.clone()); + } else { + let updated_field = match alias { + Some(alias) => field.with_updated_alias(alias.alias.clone()), + None => field.field.clone(), + }; + selection_map + .insert(Selection::from_field(updated_field, updated_selection_set)); + } + } + Selection::InlineFragment(_) => { + if selection_set == updated_selection_set.as_ref() { + selection_map.insert(selection.clone()); + } else { + selection_map + .insert(selection.with_updated_selection_set(updated_selection_set)?); + } + } + Selection::FragmentSpread(_) => { + return Err(FederationError::internal("unexpected fragment spread")) + } + } + } + + Ok(SelectionSet { + schema: self.schema.clone(), + type_position: self.type_position.clone(), + selections: Arc::new(selection_map), + }) + } + + pub(crate) fn fields_in_set(&self) -> Vec { + let mut fields = Vec::new(); + + for (_key, selection) in self.selections.iter() { + match selection { + Selection::Field(field) => fields.push(CollectedFieldInSet { + path: Vec::new(), + field: field.clone(), + }), + Selection::FragmentSpread(_fragment) => { + todo!() + } + Selection::InlineFragment(inline_fragment) => { + let condition = inline_fragment + .inline_fragment + .data() + .type_condition_position + .as_ref(); + let header = match condition { + Some(cond) => vec![FetchDataPathElement::TypenameEquals( + cond.type_name().clone().into(), + )], + None => vec![], + }; + for CollectedFieldInSet { path, field } in + inline_fragment.selection_set.fields_in_set().into_iter() + { + let mut new_path = header.clone(); + new_path.extend(path); + fields.push(CollectedFieldInSet { + path: new_path, + field, + }) + } + } + } + } + fields + } + + pub(crate) fn used_variables(&self) -> Result, FederationError> { + let mut variables = HashSet::new(); + self.collect_variables(&mut variables)?; + let mut res: Vec = variables.into_iter().cloned().collect(); + res.sort(); + Ok(res) + } + + pub(crate) fn collect_variables<'selection>( + &'selection self, + variables: &mut HashSet<&'selection Name>, + ) -> Result<(), FederationError> { + for selection in self.selections.values() { + selection.collect_variables(variables)? + } + Ok(()) + } + + pub(crate) fn validate( + &self, + _variable_definitions: &[Node], + ) -> Result<(), FederationError> { + if self.selections.is_empty() { + Err(FederationError::internal("Invalid empty selection set")) + } else { + for selection in self.selections.values() { + if let Some(s) = selection.selection_set()? { + s.validate(_variable_definitions)?; + } + } + + Ok(()) + } + } + + pub(crate) fn containment(&self, other: &Self, options: ContainmentOptions) -> Containment { + if other.selections.len() > self.selections.len() { + // If `other` has more selections but we're ignoring missing __typename, then in the case where + // `other` has a __typename but `self` does not, then we need the length of `other` to be at + // least 2 more than other of `self` to be able to conclude there is no contains. + if !options.ignore_missing_typename + || other.selections.len() > self.selections.len() + 1 + || self.has_top_level_typename_field() + || !other.has_top_level_typename_field() + { + return Containment::NotContained; + } + } + + let mut is_equal = true; + let mut did_ignore_typename = false; + + for (key, other_selection) in other.selections.iter() { + if key.is_typename_field() && options.ignore_missing_typename { + if !self.has_top_level_typename_field() { + did_ignore_typename = true; + } + continue; + } + + let Some(self_selection) = self.selections.get(key) else { + return Containment::NotContained; + }; + + match self_selection.containment(other_selection, options) { + Containment::NotContained => return Containment::NotContained, + Containment::StrictlyContained if is_equal => is_equal = false, + Containment::StrictlyContained | Containment::Equal => {} + } + } + + let expected_len = if did_ignore_typename { + self.selections.len() + 1 + } else { + self.selections.len() + }; + + if is_equal && other.selections.len() == expected_len { + Containment::Equal + } else { + Containment::StrictlyContained + } + } + + /// Returns true if this selection is a superset of the other selection. + pub(crate) fn contains(&self, other: &Self) -> bool { + self.containment(other, Default::default()).is_contained() + } +} + +#[derive(Clone)] +pub(crate) struct SelectionSetAtPath { + path: Vec, + selections: Option, +} + +pub(crate) struct FieldToAlias { + path: Vec, + response_name: NodeStr, + alias: Name, +} + +pub(crate) struct SeenResponseName { + field_name: Name, + field_type: executable::Type, + selections: Option>, +} + +pub(crate) struct CollectedFieldInSet { + path: Vec, + field: Arc, +} + +struct FieldInPath { + path: Vec, + field: Arc, +} + +fn compute_aliases_for_non_merging_fields( + selections: Vec, + alias_collector: &mut Vec, + schema: &ValidFederationSchema, +) -> Result<(), FederationError> { + let mut seen_response_names: HashMap = HashMap::new(); + + fn rebased_fields_in_set(s: &SelectionSetAtPath) -> impl Iterator + '_ { + s.selections.iter().flat_map(|s2| { + s2.fields_in_set() + .into_iter() + .map(|CollectedFieldInSet { path, field }| { + let mut new_path = s.path.clone(); + new_path.extend(path); + FieldInPath { + path: new_path, + field, + } + }) + }) + } + + for FieldInPath { mut path, field } in selections.iter().flat_map(rebased_fields_in_set) { + let field_name = field.field.data().name(); + let response_name = field.field.data().response_name(); + let field_type = &field.field.data().field_position.get(schema.schema())?.ty; + + match seen_response_names.get(&response_name) { + Some(previous) => { + if &previous.field_name == field_name + && types_can_be_merged(&previous.field_type, field_type, schema.schema())? + { + // If the type is non-composite, then we're all set. But if it is composite, we need to record the sub-selection to that response name + // as we need to "recurse" on the merged of both the previous and this new field. + if is_composite_type(field_type.inner_named_type(), schema.schema())? { + match &previous.selections { + None => { + return Err(SingleFederationError::Internal { + message: format!( + "Should have added selections for `'{:?}\'", + previous.field_type + ), + } + .into()); + } + Some(s) => { + let mut selections = s.clone(); + let mut p = path.clone(); + p.push(FetchDataPathElement::Key(response_name.clone().into())); + selections.push(SelectionSetAtPath { + path: p, + selections: field.selection_set.clone(), + }); + seen_response_names.insert( + response_name, + SeenResponseName { + field_name: previous.field_name.clone(), + field_type: previous.field_type.clone(), + selections: Some(selections), + }, + ) + } + }; + } + } else { + // We need to alias the new occurence. + let alias = gen_alias_name(&response_name, &seen_response_names); + + // Given how we generate aliases, it's is very unlikely that the generated alias will conflict with any of the other response name + // at the level, but it's theoretically possible. By adding the alias to the seen names, we ensure that in the remote change that + // this ever happen, we'll avoid the conflict by giving another alias to the followup occurence. + let selections = match field.selection_set.as_ref() { + Some(s) => { + let mut p = path.clone(); + p.push(FetchDataPathElement::Key(alias.clone().into())); + Some(vec![SelectionSetAtPath { + path: p, + selections: Some(s.clone()), + }]) + } + None => None, + }; + + seen_response_names.insert( + alias.clone(), + SeenResponseName { + field_name: field_name.clone(), + field_type: field_type.clone(), + selections, + }, + ); + + // Lastly, we record that the added alias need to be rewritten back to the proper response name post query. + + alias_collector.push(FieldToAlias { + path, + response_name: response_name.into(), + alias, + }) + } + } + None => { + let selections: Option> = match field.selection_set.as_ref() + { + Some(s) => { + path.push(FetchDataPathElement::Key(response_name.clone().into())); + Some(vec![SelectionSetAtPath { + path, + selections: Some(s.clone()), + }]) + } + None => None, + }; + seen_response_names.insert( + response_name, + SeenResponseName { + field_name: field_name.clone(), + field_type: field_type.clone(), + selections, + }, + ); + } + } + } + + for selections in seen_response_names.into_values() { + if let Some(selections) = selections.selections { + compute_aliases_for_non_merging_fields(selections, alias_collector, schema)?; + } + } + + Ok(()) +} + +fn gen_alias_name(base_name: &Name, unavailable_names: &HashMap) -> Name { + let mut counter = 0usize; + loop { + if let Ok(name) = Name::try_from(NodeStr::new(&format!("{base_name}__alias_{counter}"))) { + if !unavailable_names.contains_key(&name) { + return name; + } + } + counter += 1; + } +} + +pub(crate) fn subselection_type_if_abstract( + selection: &Selection, + schema: &ValidFederationSchema, + fragments: &Option<&mut RebasedFragments>, +) -> Option { + match selection { + Selection::Field(field) => { + match schema + .get_type(field.field.data().field_position.type_name().clone()) + .ok()? + { + crate::schema::position::TypeDefinitionPosition::Interface(i) => { + Some(AbstractType::Interface(i)) + } + crate::schema::position::TypeDefinitionPosition::Union(u) => { + Some(AbstractType::Union(u)) + } + _ => None, + } + } + Selection::FragmentSpread(fragment_spread) => { + let fragment = fragments + .as_ref() + .and_then(|r| { + r.original_fragments + .get(&fragment_spread.spread.data().fragment_name) + }) + .ok_or(FederationError::SingleFederationError( + crate::error::SingleFederationError::InvalidGraphQL { + message: "missing fragment".to_string(), + }, + )) + //FIXME: return error + .ok()?; + match fragment.type_condition_position.clone() { + CompositeTypeDefinitionPosition::Interface(i) => Some(AbstractType::Interface(i)), + CompositeTypeDefinitionPosition::Union(u) => Some(AbstractType::Union(u)), + CompositeTypeDefinitionPosition::Object(_) => None, + } + } + Selection::InlineFragment(inline_fragment) => { + match inline_fragment + .inline_fragment + .data() + .type_condition_position + .clone()? + { + CompositeTypeDefinitionPosition::Interface(i) => Some(AbstractType::Interface(i)), + CompositeTypeDefinitionPosition::Union(u) => Some(AbstractType::Union(u)), + CompositeTypeDefinitionPosition::Object(_) => None, + } + } + } +} + +impl FieldSelection { + /// Normalize this field selection (merging selections with the same keys), with the following + /// additional transformations: + /// - Expand fragment spreads into inline fragments. + /// - Remove `__schema` or `__type` introspection fields, as these shouldn't be handled by query + /// planning. + /// - Hoist fragment spreads/inline fragments into their parents if they have no directives and + /// their parent type matches. + pub(crate) fn from_field( + field: &executable::Field, + parent_type_position: &CompositeTypeDefinitionPosition, + fragments: &NamedFragments, + schema: &ValidFederationSchema, + ) -> Result, FederationError> { + // Skip __schema/__type introspection fields as router takes care of those, and they do not + // need to be query planned. + if field.name == "__schema" || field.name == "__type" { + return Ok(None); + } + let field_position = parent_type_position.field(field.name.clone())?; + // We might be able to validate that the returned `FieldDefinition` matches that within + // the given `field`, but on the off-chance there's a mutation somewhere in between + // Operation creation and the creation of the ValidFederationSchema, it's safer to just + // confirm it exists in this schema. + field_position.get(schema.schema())?; + let field_composite_type_result: Result = + schema.get_type(field.selection_set.ty.clone())?.try_into(); + + Ok(Some(FieldSelection { + field: Field::new(FieldData { + schema: schema.clone(), + field_position, + alias: field.alias.clone(), + arguments: Arc::new(field.arguments.clone()), + directives: Arc::new(field.directives.clone()), + sibling_typename: None, + }), + selection_set: if field_composite_type_result.is_ok() { + Some(SelectionSet::from_selection_set( + &field.selection_set, + fragments, + schema, + )?) + } else { + None + }, + })) + } + + pub(crate) fn normalize( + &self, + parent_type: &CompositeTypeDefinitionPosition, + named_fragments: &NamedFragments, + schema: &ValidFederationSchema, + option: NormalizeSelectionOption, + ) -> Result, FederationError> { + if let Some(selection_set) = &self.selection_set { + let mut normalized_selection: SelectionSet = + if NormalizeSelectionOption::NormalizeRecursively == option { + let field = self.field.data().field_position.get(schema.schema())?; + let field_composite_type_position: CompositeTypeDefinitionPosition = schema + .get_type(field.ty.inner_named_type().clone())? + .try_into()?; + selection_set.normalize( + &field_composite_type_position, + named_fragments, + schema, + option, + )? + } else { + selection_set.clone() + }; + + let mut selection = self.clone(); + if normalized_selection.is_empty() { + // In rare cases, it's possible that everything in the sub-selection was trimmed away and so the + // sub-selection is empty. Which suggest something may be wrong with this part of the query + // intent, but the query was valid while keeping an empty sub-selection isn't. So in that + // case, we just add some "non-included" __typename field just to keep the query valid. + let directives = + executable::DirectiveList(vec![Node::new(executable::Directive { + name: name!("include"), + arguments: vec![Node::new(executable::Argument { + name: name!("if"), + value: Node::new(executable::Value::Boolean(false)), + })], + })]); + let non_included_typename = Selection::from_field( + Field::new(FieldData { + schema: schema.clone(), + field_position: parent_type.introspection_typename_field(), + alias: None, + arguments: Arc::new(vec![]), + directives: Arc::new(directives), + sibling_typename: None, + }), + None, + ); + let mut typename_selection = SelectionMap::new(); + typename_selection.insert(non_included_typename); + + normalized_selection.selections = Arc::new(typename_selection); + selection.selection_set = Some(normalized_selection); + } else { + selection.selection_set = Some(normalized_selection); + } + Ok(Some(SelectionOrSet::Selection(Selection::from(selection)))) + } else { + // JS PORT NOTE: In JS implementation field selection stores field definition information, + // in RS version we only store the field position reference so we don't need to update the + // underlying elements + Ok(Some(SelectionOrSet::Selection(Selection::from( + self.clone(), + )))) + } + } + + /// Returns a field selection "equivalent" to the one represented by this object, but such that its parent type + /// is the one provided as argument. + /// + /// Obviously, this operation will only succeed if this selection (both the field itself and its subselections) + /// make sense from the provided parent type. If this is not the case, this method will throw. + pub(crate) fn rebase_on( + &self, + parent_type: &CompositeTypeDefinitionPosition, + named_fragments: &NamedFragments, + schema: &ValidFederationSchema, + error_handling: RebaseErrorHandlingOption, + ) -> Result, FederationError> { + if &self.field.data().schema == schema + && &self.field.data().field_position.parent() == parent_type + { + // we are rebasing field on the same parent within the same schema - we can just return self + return Ok(Some(Selection::from(self.clone()))); + } + + let Some(rebased) = self.field.rebase_on(parent_type, schema, error_handling)? else { + // rebasing failed but we are ignoring errors + return Ok(None); + }; + + let Some(selection_set) = &self.selection_set else { + // leaf field + return Ok(Some(Selection::from_field(rebased, None))); + }; + + let rebased_type_name = rebased + .data() + .field_position + .get(schema.schema())? + .ty + .inner_named_type(); + let rebased_base_type: CompositeTypeDefinitionPosition = + schema.get_type(rebased_type_name.clone())?.try_into()?; + + let selection_set_type = &selection_set.type_position; + if self.field.data().schema == rebased.data().schema + && &rebased_base_type == selection_set_type + { + // we are rebasing within the same schema and the same base type + return Ok(Some(Selection::from_field( + rebased.clone(), + self.selection_set.clone(), + ))); + } + + let rebased_selection_set = + selection_set.rebase_on(&rebased_base_type, named_fragments, schema, error_handling)?; + if rebased_selection_set.selections.is_empty() { + // empty selection set + Ok(None) + } else { + Ok(Some(Selection::from_field( + rebased.clone(), + Some(rebased_selection_set), + ))) + } + } + + fn can_add_to( + &self, + parent_type: &CompositeTypeDefinitionPosition, + schema: &ValidFederationSchema, + ) -> bool { + if &self.field.data().schema == schema + && parent_type == &self.field.data().field_position.parent() + { + return true; + } + + let Some(ty) = self.field.type_if_added_to(parent_type, schema) else { + return false; + }; + + if let Some(set) = &self.selection_set { + if set.type_position != ty { + return set + .selections + .values() + .all(|sel| sel.can_add_to(parent_type, schema)); + } + } + true + } + + pub(crate) fn has_defer(&self) -> bool { + self.field.has_defer() || self.selection_set.as_ref().is_some_and(|s| s.has_defer()) + } + + pub(crate) fn containment( + &self, + other: &FieldSelection, + options: ContainmentOptions, + ) -> Containment { + let self_field = self.field.data(); + let other_field = other.field.data(); + if self_field.name() != other_field.name() + || self_field.alias != other_field.alias + || !same_arguments(&self_field.arguments, &other_field.arguments) + || !same_directives(&self_field.directives, &other_field.directives) + { + return Containment::NotContained; + } + + match (&self.selection_set, &other.selection_set) { + (None, None) => Containment::Equal, + (Some(self_selection), Some(other_selection)) => { + self_selection.containment(other_selection, options) + } + (None, Some(_)) | (Some(_), None) => { + debug_assert!(false, "field selections have the same element, so if one does not have a subselection, neither should the other one"); + Containment::NotContained + } + } + } + + /// Returns true if this selection is a superset of the other selection. + pub(crate) fn contains(&self, other: &FieldSelection) -> bool { + self.containment(other, Default::default()).is_contained() + } +} + +impl<'a> FieldSelectionValue<'a> { + /// Merges the given normalized field selections into this one (this method assumes the keys + /// already match). + pub(crate) fn merge_into<'op>( + &mut self, + others: impl Iterator, + ) -> Result<(), FederationError> { + let self_field = &self.get().field; + let mut selection_sets = vec![]; + for other in others { + let other_field = &other.field; + if other_field.data().schema != self_field.data().schema { + return Err(Internal { + message: "Cannot merge field selections from different schemas".to_owned(), + } + .into()); + } + if other_field.data().field_position != self_field.data().field_position { + return Err(Internal { + message: format!( + "Cannot merge field selection for field \"{}\" into a field selection for field \"{}\"", + other_field.data().field_position, + self_field.data().field_position, + ), + }.into()); + } + if self.get().selection_set.is_some() { + let Some(other_selection_set) = &other.selection_set else { + return Err(Internal { + message: format!( + "Field \"{}\" has composite type but not a selection set", + other_field.data().field_position, + ), + } + .into()); + }; + selection_sets.push(other_selection_set); + } else if other.selection_set.is_some() { + return Err(Internal { + message: format!( + "Field \"{}\" has non-composite type but also has a selection set", + other_field.data().field_position, + ), + } + .into()); + } + } + if let Some(self_selection_set) = self.get_selection_set_mut() { + self_selection_set.merge_into(selection_sets.into_iter())?; + } + Ok(()) + } +} + +impl Field { + pub(crate) fn rebase_on( + &self, + parent_type: &CompositeTypeDefinitionPosition, + schema: &ValidFederationSchema, + error_handling: RebaseErrorHandlingOption, + ) -> Result, FederationError> { + let field_parent = self.data().field_position.parent(); + if self.data().schema == *schema && field_parent == *parent_type { + // pointing to the same parent -> return self + return Ok(Some(self.clone())); + } + + if self.data().name() == &TYPENAME_FIELD { + // TODO interface object info should be precomputed in QP constructor + return if schema + .possible_runtime_types(parent_type.clone())? + .iter() + .any(|t| is_interface_object(t, schema)) + { + if let RebaseErrorHandlingOption::ThrowError = error_handling { + Err(FederationError::internal( + format!("Cannot add selection of field \"{}\" to selection set of parent type \"{}\" that is potentially an interface object type at runtime", + self.data().field_position, + parent_type + ))) + } else { + Ok(None) + } + } else { + let mut updated_field_data = self.data().clone(); + updated_field_data.schema = schema.clone(); + updated_field_data.field_position = parent_type.introspection_typename_field(); + Ok(Some(Field::new(updated_field_data))) + }; + } + + let field_from_parent = parent_type.field(self.data().name().clone())?; + return if field_from_parent.get(schema.schema()).is_ok() + && self.can_rebase_on(parent_type, schema) + { + let mut updated_field_data = self.data().clone(); + updated_field_data.schema = schema.clone(); + updated_field_data.field_position = field_from_parent; + Ok(Some(Field::new(updated_field_data))) + } else if let RebaseErrorHandlingOption::IgnoreError = error_handling { + Ok(None) + } else { + Err(FederationError::internal(format!( + "Cannot add selection of field \"{}\" to selection set of parent type \"{}\"", + self.data().field_position, + parent_type + ))) + }; + } + + /// Verifies whether given field can be rebase on following parent type. + /// + /// There are 2 valid cases we want to allow: + /// 1. either `parent_type` and `field_parent_type` are the same underlying type (same name) but from different underlying schema. Typically, + /// happens when we're building subgraph queries but using selections from the original query which is against the supergraph API schema. + /// 2. or they are not the same underlying type, but the field parent type is from an interface (or an interface object, which is the same + /// here), in which case we may be rebasing an interface field on one of the implementation type, which is ok. Note that we don't verify + /// that `parent_type` is indeed an implementation of `field_parent_type` because it's possible that this implementation relationship exists + /// in the supergraph, but not in any of the subgraph schema involved here. So we just let it be. Not that `rebase_on` will complain anyway + /// if the field name simply does not exist in `parent_type`. + fn can_rebase_on( + &self, + parent_type: &CompositeTypeDefinitionPosition, + schema: &ValidFederationSchema, + ) -> bool { + let field_parent_type = self.data().field_position.parent(); + // case 1 + if field_parent_type.type_name() == parent_type.type_name() { + return true; + } + // case 2 + let is_interface_object_type = + match TryInto::::try_into(field_parent_type.clone()) { + Ok(ref o) => is_interface_object(o, schema), + Err(_) => false, + }; + field_parent_type.is_interface_type() || is_interface_object_type + } + + pub(crate) fn has_defer(&self) -> bool { + // @defer cannot be on field at the moment + false + } + + pub(crate) fn type_if_added_to( + &self, + parent_type: &CompositeTypeDefinitionPosition, + schema: &ValidFederationSchema, + ) -> Option { + let data = self.data(); + if data.field_position.parent() == *parent_type && data.schema == *schema { + let base_ty_name = data + .field_position + .get(schema.schema()) + .ok()? + .ty + .inner_named_type(); + return schema + .get_type(base_ty_name.clone()) + .and_then(CompositeTypeDefinitionPosition::try_from) + .ok(); + } + if data.name() == &TYPENAME_FIELD { + let type_name = parent_type + .introspection_typename_field() + .get(schema.schema()) + .ok()? + .ty + .inner_named_type(); + return schema.try_get_type(type_name.clone())?.try_into().ok(); + } + if self.can_rebase_on(parent_type, schema) { + let type_name = parent_type + .field(data.field_position.field_name().clone()) + .ok()? + .get(schema.schema()) + .ok()? + .ty + .inner_named_type(); + schema.try_get_type(type_name.clone())?.try_into().ok() + } else { + None + } + } +} + +impl<'a> FragmentSpreadSelectionValue<'a> { + /// Merges the given normalized fragment spread selections into this one (this method assumes + /// the keys already match). + pub(crate) fn merge_into<'op>( + &mut self, + others: impl Iterator, + ) -> Result<(), FederationError> { + let self_fragment_spread = &self.get().spread; + for other in others { + let other_fragment_spread = &other.spread; + if other_fragment_spread.data().schema != self_fragment_spread.data().schema { + return Err(Internal { + message: "Cannot merge fragment spread from different schemas".to_owned(), + } + .into()); + } + // Nothing to do since the fragment spread is already part of the selection set. + // Fragment spreads are uniquely identified by fragment name and applied directives. + // Since there is already an entry for the same fragment spread, there is no point + // in attempting to merge its sub-selections, as the underlying entry should be + // exactly the same as the currently processed one. + } + Ok(()) + } +} + +impl InlineFragmentSelection { + /// Copies inline fragment selection and assigns it a new unique selection ID. + pub(crate) fn with_unique_id(&self) -> Self { + let mut data = self.inline_fragment.data().clone(); + data.selection_id = SelectionId::new(); + Self { + inline_fragment: InlineFragment::new(data), + selection_set: self.selection_set.clone(), + } + } + + /// Normalize this inline fragment selection (merging selections with the same keys), with the + /// following additional transformations: + /// - Expand fragment spreads into inline fragments. + /// - Remove `__schema` or `__type` introspection fields, as these shouldn't be handled by query + /// planning. + /// - Hoist fragment spreads/inline fragments into their parents if they have no directives and + /// their parent type matches. + pub(crate) fn from_inline_fragment( + inline_fragment: &executable::InlineFragment, + parent_type_position: &CompositeTypeDefinitionPosition, + fragments: &NamedFragments, + schema: &ValidFederationSchema, + ) -> Result { + let type_condition_position: Option = + if let Some(type_condition) = &inline_fragment.type_condition { + Some(schema.get_type(type_condition.clone())?.try_into()?) + } else { + None + }; + Ok(InlineFragmentSelection { + inline_fragment: InlineFragment::new(InlineFragmentData { + schema: schema.clone(), + parent_type_position: parent_type_position.clone(), + type_condition_position, + directives: Arc::new(inline_fragment.directives.clone()), + selection_id: SelectionId::new(), + }), + selection_set: SelectionSet::from_selection_set( + &inline_fragment.selection_set, + fragments, + schema, + )?, + }) + } + + pub(crate) fn from_fragment_spread_selection( + fragment_spread_selection: &Arc, + ) -> Result { + let fragment_spread_data = fragment_spread_selection.spread.data(); + Ok(InlineFragmentSelection { + inline_fragment: InlineFragment::new(InlineFragmentData { + schema: fragment_spread_data.schema.clone(), + parent_type_position: fragment_spread_data.type_condition_position.clone(), + type_condition_position: Some(fragment_spread_data.type_condition_position.clone()), + directives: fragment_spread_data.directives.clone(), + selection_id: SelectionId::new(), + }), + selection_set: fragment_spread_selection + .selection_set + .expand_all_fragments()?, + }) + } + + pub(crate) fn normalize( + &self, + parent_type: &CompositeTypeDefinitionPosition, + named_fragments: &NamedFragments, + schema: &ValidFederationSchema, + option: NormalizeSelectionOption, + ) -> Result, FederationError> { + let this_condition = self.inline_fragment.data().type_condition_position.clone(); + // This method assumes by contract that `parent_type` runtimes intersects `self.inline_fragment.data().parent_type_position`'s, + // but `parent_type` runtimes may be a subset. So first check if the selection should not be discarded on that account (that + // is, we should not keep the selection if its condition runtimes don't intersect at all with those of + // `parent_type` as that would ultimately make an invalid selection set). + if let Some(ref type_condition) = this_condition { + if (self.inline_fragment.data().schema != *schema + || self.inline_fragment.data().parent_type_position != *parent_type) + && !runtime_types_intersect(type_condition, parent_type, schema) + { + return Ok(None); + } + } + + // We know the condition is "valid", but it may not be useful. That said, if the condition has directives, + // we preserve the fragment no matter what. + if self.inline_fragment.data().directives.is_empty() { + // There is a number of cases where a fragment is not useful: + // 1. if there is no type condition (remember it also has no directives). + // 2. if it's the same type as the current type: it's not restricting types further. + // 3. if the current type is an object more generally: because in that case the condition + // cannot be restricting things further (it's typically a less precise interface/union). + let useless_fragment = match this_condition { + None => true, + Some(ref c) => self.inline_fragment.data().schema == *schema && c == parent_type, + }; + if useless_fragment || parent_type.is_object_type() { + let normalized_selection_set = + self.selection_set + .normalize(parent_type, named_fragments, schema, option)?; + return if normalized_selection_set.is_empty() { + Ok(None) + } else { + Ok(Some(SelectionOrSet::SelectionSet(normalized_selection_set))) + }; + } + } + + // We preserve the current fragment, so we only recurse within the sub-selection if we're asked to be recursive. + // (note that even if we're not recursive, we may still have some "lifting" to do) + let normalized_selection_set = if NormalizeSelectionOption::NormalizeRecursively == option { + let normalized = + self.selection_set + .normalize(parent_type, named_fragments, schema, option)?; + // It could be that nothing was satisfiable. + if normalized.is_empty() { + if self.inline_fragment.data().directives.is_empty() { + return Ok(None); + } else if let Some(rebased_fragment) = self.inline_fragment.rebase_on( + parent_type, + schema, + RebaseErrorHandlingOption::ThrowError, + )? { + // We should be able to rebase, or there is a bug, so error if that is the case. + // If we rebased successfully then we add "non-included" __typename field selection + // just to keep the query valid. + let directives = + executable::DirectiveList(vec![Node::new(executable::Directive { + name: name!("include"), + arguments: vec![Node::new(executable::Argument { + name: name!("if"), + value: Node::new(executable::Value::Boolean(false)), + })], + })]); + let parent_typename_field = if let Some(condition) = this_condition { + condition.introspection_typename_field() + } else { + parent_type.introspection_typename_field() + }; + let typename_field_selection = Selection::from_field( + Field::new(FieldData { + schema: schema.clone(), + field_position: parent_typename_field, + alias: None, + arguments: Arc::new(vec![]), + directives: Arc::new(directives), + sibling_typename: None, + }), + None, + ); + + return Ok(Some(SelectionOrSet::Selection( + Selection::from_inline_fragment( + rebased_fragment, + SelectionSet::from_selection( + parent_type.clone(), + typename_field_selection, + ), + ), + ))); + } + } + normalized + } else { + self.selection_set.clone() + }; + + // Second, we check if some of the sub-selection fragments can be "lifted" outside of this fragment. This can happen if: + // 1. the current fragment is an abstract type, + // 2. the sub-fragment is an object type, + // 3. the sub-fragment type is a valid runtime of the current type. + if self.inline_fragment.data().directives.is_empty() + && this_condition.is_some_and(|c| c.is_abstract_type()) + { + let mut liftable_selections = SelectionMap::new(); + for (_, selection) in normalized_selection_set.selections.iter() { + match selection { + Selection::FragmentSpread(spread_selection) => { + let type_condition = spread_selection + .spread + .data() + .type_condition_position + .clone(); + if type_condition.is_object_type() + && runtime_types_intersect(parent_type, &type_condition, schema) + { + liftable_selections + .insert(Selection::FragmentSpread(spread_selection.clone())); + } + } + Selection::InlineFragment(inline_fragment_selection) => { + if let Some(type_condition) = inline_fragment_selection + .inline_fragment + .data() + .type_condition_position + .clone() + { + if type_condition.is_object_type() + && runtime_types_intersect(parent_type, &type_condition, schema) + { + liftable_selections.insert(Selection::InlineFragment( + inline_fragment_selection.clone(), + )); + } + }; + } + _ => continue, + } + } + + // If we can lift all selections, then that just mean we can get rid of the current fragment altogether + if liftable_selections.len() == normalized_selection_set.selections.len() { + return Ok(Some(SelectionOrSet::SelectionSet(normalized_selection_set))); + } + + // Otherwise, if there are "liftable" selections, we must return a set comprised of those lifted selection, + // and the current fragment _without_ those lifted selections. + if liftable_selections.len() > 0 { + let mut mutable_selections = self.selection_set.selections.clone(); + let final_fragment_selections = Arc::make_mut(&mut mutable_selections); + final_fragment_selections.retain(|k, _| !liftable_selections.contains_key(k)); + let final_inline_fragment = Selection::from_inline_fragment( + self.inline_fragment.clone(), + SelectionSet { + schema: schema.clone(), + type_position: parent_type.clone(), + selections: Arc::new(final_fragment_selections.clone()), + }, + ); + + let mut final_selection_map = SelectionMap::new(); + final_selection_map.insert(final_inline_fragment); + final_selection_map.extend(liftable_selections); + let final_selections = SelectionSet { + schema: schema.clone(), + type_position: parent_type.clone(), + selections: final_selection_map.into(), + }; + return Ok(Some(SelectionOrSet::SelectionSet(final_selections))); + } + } + + if self.inline_fragment.data().schema == *schema + && self.inline_fragment.data().parent_type_position == *parent_type + && self.selection_set == normalized_selection_set + { + // normalization did not change the fragment + Ok(Some(SelectionOrSet::Selection(Selection::InlineFragment( + Arc::new(self.clone()), + )))) + } else if let Some(rebased) = self.inline_fragment.rebase_on( + parent_type, + schema, + RebaseErrorHandlingOption::ThrowError, + )? { + Ok(Some(SelectionOrSet::Selection(Selection::InlineFragment( + Arc::new(InlineFragmentSelection { + inline_fragment: rebased, + selection_set: normalized_selection_set, + }), + )))) + } else { + unreachable!("We should always be able to either rebase the inline fragment OR throw an exception"); + } + } + + pub(crate) fn rebase_on( + &self, + parent_type: &CompositeTypeDefinitionPosition, + named_fragments: &NamedFragments, + schema: &ValidFederationSchema, + error_handling: RebaseErrorHandlingOption, + ) -> Result, FederationError> { + if &self.inline_fragment.data().schema == schema + && self.inline_fragment.data().parent_type_position == *parent_type + { + // we are rebasing inline fragment on the same parent within the same schema - we can just return self + return Ok(Some(Selection::from(self.clone()))); + } + + let Some(rebased_fragment) = + self.inline_fragment + .rebase_on(parent_type, schema, error_handling)? + else { + // rebasing failed but we are ignoring errors + return Ok(None); + }; + + let rebased_casted_type = rebased_fragment + .data() + .type_condition_position + .clone() + .unwrap_or(rebased_fragment.data().parent_type_position.clone()); + if &self.inline_fragment.data().schema == schema && rebased_casted_type == *parent_type { + // we are within the same schema - selection set does not have to be rebased + Ok(Some(Selection::from_inline_fragment( + rebased_fragment, + self.selection_set.clone(), + ))) + } else { + let rebased_selection_set = self.selection_set.rebase_on( + &rebased_casted_type, + named_fragments, + schema, + error_handling, + )?; + if rebased_selection_set.selections.is_empty() { + // empty selection set + Ok(None) + } else { + Ok(Some(Selection::from_inline_fragment( + rebased_fragment, + rebased_selection_set, + ))) + } + } + } + + pub(crate) fn can_add_to( + &self, + parent_type: &CompositeTypeDefinitionPosition, + schema: &ValidFederationSchema, + ) -> bool { + if &self.inline_fragment.data().parent_type_position == parent_type + && self.inline_fragment.data().schema == *schema + { + return true; + } + let Some(ty) = self + .inline_fragment + .data() + .casted_type_if_add_to(parent_type, schema) + else { + return false; + }; + if self.selection_set.type_position != ty { + for sel in self.selection_set.selections.values() { + if !sel.can_add_to(&ty, schema) { + return false; + } + } + true + } else { + true + } + } + + pub(crate) fn can_rebase_on( + &self, + parent_type: &CompositeTypeDefinitionPosition, + parent_schema: &ValidFederationSchema, + ) -> bool { + self.inline_fragment + .can_rebase_on(parent_type, parent_schema) + .0 + } + + pub(crate) fn casted_type(&self) -> &CompositeTypeDefinitionPosition { + let data = self.inline_fragment.data(); + data.type_condition_position + .as_ref() + .unwrap_or(&data.parent_type_position) + } + + pub(crate) fn has_defer(&self) -> bool { + self.inline_fragment.data().directives.has("defer") + || self + .selection_set + .selections + .values() + .any(|s| s.has_defer()) + } + + pub(crate) fn containment( + &self, + other: &Selection, + options: ContainmentOptions, + ) -> Containment { + match other { + // Using keys here means that @defer fragments never compare equal. + // This is a bit odd but it is consistent: the selection set data structure would not + // even try to compare two @defer fragments, because their keys are different. + Selection::InlineFragment(other) + if self.inline_fragment.key() == other.inline_fragment.key() => + { + self.selection_set + .containment(&other.selection_set, options) + } + _ => Containment::NotContained, + } + } + + /// Returns true if this selection is a superset of the other selection. + pub(crate) fn contains(&self, other: &Selection) -> bool { + self.containment(other, Default::default()).is_contained() + } +} + +impl<'a> InlineFragmentSelectionValue<'a> { + /// Merges the given normalized inline fragment selections into this one (this method assumes + /// the keys already match). + pub(crate) fn merge_into<'op>( + &mut self, + others: impl Iterator, + ) -> Result<(), FederationError> { + let self_inline_fragment = &self.get().inline_fragment; + let mut selection_sets = vec![]; + for other in others { + let other_inline_fragment = &other.inline_fragment; + if other_inline_fragment.data().schema != self_inline_fragment.data().schema { + return Err(Internal { + message: "Cannot merge inline fragment from different schemas".to_owned(), + } + .into()); + } + if other_inline_fragment.data().parent_type_position + != self_inline_fragment.data().parent_type_position + { + return Err(Internal { + message: format!( + "Cannot merge inline fragment of parent type \"{}\" into an inline fragment of parent type \"{}\"", + other_inline_fragment.data().parent_type_position, + self_inline_fragment.data().parent_type_position, + ), + }.into()); + } + selection_sets.push(&other.selection_set); + } + self.get_selection_set_mut() + .merge_into(selection_sets.into_iter())?; + Ok(()) + } +} + +impl InlineFragment { + pub(crate) fn rebase_on( + &self, + parent_type: &CompositeTypeDefinitionPosition, + schema: &ValidFederationSchema, + error_handling: RebaseErrorHandlingOption, + ) -> Result, FederationError> { + if &self.data().parent_type_position == parent_type { + return Ok(Some(self.clone())); + } + + let type_condition = self.data().type_condition_position.clone(); + // This usually imply that the fragment is not from the same subgraph than the selection. So we need + // to update the source type of the fragment, but also "rebase" the condition to the selection set + // schema. + let (can_rebase, rebased_condition) = self.can_rebase_on(parent_type, schema); + if !can_rebase { + if let RebaseErrorHandlingOption::ThrowError = error_handling { + let printable_type_condition = self + .data() + .type_condition_position + .clone() + .map_or_else(|| "".to_string(), |t| t.to_string()); + let printable_runtimes = type_condition.map_or_else( + || "undefined".to_string(), + |t| print_possible_runtimes(&t, schema), + ); + let printable_parent_runtimes = print_possible_runtimes(parent_type, schema); + Err(FederationError::internal( + format!("Cannot add fragment of condition \"{}\" (runtimes: [{}]) to parent type \"{}\" (runtimes: [{})", + printable_type_condition, + printable_runtimes, + parent_type, + printable_parent_runtimes, + ), + )) + } else { + Ok(None) + } + } else { + let mut rebased_fragment_data = self.data().clone(); + rebased_fragment_data.type_condition_position = rebased_condition; + Ok(Some(InlineFragment::new(rebased_fragment_data))) + } + } + + pub(crate) fn can_rebase_on( + &self, + parent_type: &CompositeTypeDefinitionPosition, + parent_schema: &ValidFederationSchema, + ) -> (bool, Option) { + if self.data().type_condition_position.is_none() { + // can_rebase = true, condition = undefined + return (true, None); + } + + if let Some(Ok(rebased_condition)) = self + .data() + .type_condition_position + .clone() + .and_then(|condition_position| { + parent_schema.try_get_type(condition_position.type_name().clone()) + }) + .map(|rebased_condition_position| { + CompositeTypeDefinitionPosition::try_from(rebased_condition_position) + }) + { + // chained if let chains are not yet supported + // see https://github.com/rust-lang/rust/issues/53667 + if runtime_types_intersect(parent_type, &rebased_condition, parent_schema) { + // can_rebase = true, condition = rebased_condition + (true, Some(rebased_condition)) + } else { + (false, None) + } + } else { + // can_rebase = false, condition = undefined + (false, None) + } + } +} + +pub(crate) fn merge_selection_sets( + mut selection_sets: Vec, +) -> Result { + let Some((first, remainder)) = selection_sets.split_first_mut() else { + return Err(Internal { + message: "".to_owned(), + } + .into()); + }; + first.merge_into(remainder.iter())?; + + // Take ownership of the first element and discard the rest; + // we can unwrap because `split_first_mut()` guarantees at least one element will be yielded + Ok(selection_sets.into_iter().next().unwrap()) +} + +/// Options for handling rebasing errors. +#[derive(Clone, Copy)] +pub(crate) enum RebaseErrorHandlingOption { + IgnoreError, + ThrowError, +} + +/// Options for normalizing the selection sets +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum NormalizeSelectionOption { + NormalizeRecursively, + NormalizeSingleSelection, +} + +/// This uses internal copy-on-write optimization to make `Clone` cheap. +/// However a cloned `NamedFragments` still behaves like a deep copy: +/// unlike in JS where we can have multiple references to a mutable map, +/// here modifying a cloned map will leave the original unchanged. +#[derive(Clone, Debug, PartialEq, Eq, Default)] +pub(crate) struct NamedFragments { + fragments: Arc>>, +} + +impl NamedFragments { + pub(crate) fn new( + fragments: &IndexMap>, + schema: &ValidFederationSchema, + ) -> NamedFragments { + // JS PORT - In order to normalize Fragments we need to process them in dependency order. + // + // In JS implementation mapInDependencyOrder method was called when rebasing/filtering/expanding selection sets. + // Since resulting `IndexMap` of `NormalizedFragments` will be already sorted, we only need to map it once + // when creating the `NamedFragments`. + NamedFragments::initialize_in_dependency_order(fragments, schema) + } + + pub(crate) fn is_empty(&self) -> bool { + self.fragments.len() == 0 + } + + pub(crate) fn size(&self) -> usize { + self.fragments.len() + } + + pub(crate) fn insert(&mut self, fragment: Fragment) { + Arc::make_mut(&mut self.fragments).insert(fragment.name.clone(), Node::new(fragment)); + } + + pub(crate) fn try_insert(&mut self, fragment: Fragment) -> Result<(), FederationError> { + match Arc::make_mut(&mut self.fragments).entry(fragment.name.clone()) { + indexmap::map::Entry::Occupied(_) => { + Err(FederationError::internal("Duplicate fragment name")) + } + indexmap::map::Entry::Vacant(entry) => { + let _ = entry.insert(Node::new(fragment)); + Ok(()) + } + } + } + + pub(crate) fn get(&self, name: &Name) -> Option> { + self.fragments.get(name).cloned() + } + + pub(crate) fn contains(&self, name: &Name) -> bool { + self.fragments.contains_key(name) + } + + /** + * Collect the usages of fragments that are used within the selection of other fragments. + */ + pub(crate) fn collect_used_fragment_names(&self, aggregator: &mut HashMap) { + for fragment in self.fragments.values() { + fragment + .selection_set + .collect_used_fragment_names(aggregator); + } + } + + /// JS PORT NOTE: In JS implementation this method was named mapInDependencyOrder and accepted a lambda to + /// apply transformation on the fragments. It was called when rebasing/filtering/expanding selection sets. + /// JS PORT NOTE: In JS implementation this method was potentially returning `undefined`. In order to simplify the code + /// we will always return `NamedFragments` even if they are empty. + /// + /// We normalize passed in fragments in their dependency order, i.e. if a fragment A uses another fragment B, then we will + /// normalize B _before_ attempting to normalize A. Normalized fragments have access to previously normalized fragments. + fn initialize_in_dependency_order( + fragments: &IndexMap>, + schema: &ValidFederationSchema, + ) -> NamedFragments { + struct FragmentDependencies { + fragment: Node, + depends_on: Vec, + } + + let mut fragments_map: HashMap = HashMap::new(); + for fragment in fragments.values() { + let mut fragment_usages: HashMap = HashMap::new(); + NamedFragments::collect_fragment_usages(&fragment.selection_set, &mut fragment_usages); + let usages: Vec = fragment_usages.keys().cloned().collect::>(); + fragments_map.insert( + fragment.name.clone(), + FragmentDependencies { + fragment: fragment.clone(), + depends_on: usages, + }, + ); + } + + let mut removed_fragments: HashSet = HashSet::new(); + let mut mapped_fragments = NamedFragments::default(); + while !fragments_map.is_empty() { + // Note that graphQL specifies that named fragments cannot have cycles (https://spec.graphql.org/draft/#sec-Fragment-spreads-must-not-form-cycles) + // and so we're guaranteed that on every iteration, at least one element of the map is removed (so the `while` loop will terminate). + fragments_map.retain(|name, info| { + let can_remove = info + .depends_on + .iter() + .all(|n| mapped_fragments.contains(n) || removed_fragments.contains(n)); + if can_remove { + if let Ok(normalized) = + Fragment::from_fragment(&info.fragment, &mapped_fragments, schema) + { + // TODO this actually throws in JS code -> should we also throw? + // JS code has methods for + // * add and throw exception if entry already there + // * add_if_not_exists + // Rust HashMap exposes insert (that overwrites) and try_insert (that throws) + mapped_fragments.insert(normalized); + } else { + removed_fragments.insert(name.clone()); + } + } + // keep only the elements that cannot be removed + !can_remove + }); + } + mapped_fragments + } + + // JS PORT - we need to calculate those for both executable::SelectionSet and SelectionSet + fn collect_fragment_usages( + selection_set: &executable::SelectionSet, + aggregator: &mut HashMap, + ) { + selection_set.selections.iter().for_each(|s| match s { + executable::Selection::Field(f) => { + NamedFragments::collect_fragment_usages(&f.selection_set, aggregator); + } + executable::Selection::InlineFragment(i) => { + NamedFragments::collect_fragment_usages(&i.selection_set, aggregator); + } + executable::Selection::FragmentSpread(f) => { + let current_count = aggregator.entry(f.fragment_name.clone()).or_default(); + *current_count += 1; + } + }) + } + + /// When we rebase named fragments on a subgraph schema, only a subset of what the fragment handles may belong + /// to that particular subgraph. And there are a few sub-cases where that subset is such that we basically need or + /// want to consider to ignore the fragment for that subgraph, and that is when: + /// 1. the subset that apply is actually empty. The fragment wouldn't be valid in this case anyway. + /// 2. the subset is a single leaf field: in that case, using the one field directly is just shorter than using + /// the fragment, so we consider the fragment don't really apply to that subgraph. Technically, using the + /// fragment could still be of value if the fragment name is a lot smaller than the one field name, but it's + /// enough of a niche case that we ignore it. Note in particular that one sub-case of this rule that is likely + /// to be common is when the subset ends up being just `__typename`: this would basically mean the fragment + /// don't really apply to the subgraph, and that this will ensure this is the case. + pub(crate) fn is_selection_set_worth_using(selection_set: &SelectionSet) -> bool { + if selection_set.selections.len() == 0 { + return false; + } + if selection_set.selections.len() == 1 { + // true if NOT field selection OR non-leaf field + return if let Some((_, Selection::Field(field_selection))) = + selection_set.selections.first() + { + field_selection.selection_set.is_some() + } else { + true + }; + } + true + } + + pub(crate) fn rebase_on( + &self, + schema: &ValidFederationSchema, + ) -> Result { + let mut rebased_fragments = NamedFragments::default(); + for fragment in self.fragments.values() { + if let Ok(rebased_type) = schema + .get_type(fragment.type_condition_position.type_name().clone()) + .and_then(CompositeTypeDefinitionPosition::try_from) + { + if let Ok(mut rebased_selection) = fragment.selection_set.rebase_on( + &rebased_type, + &rebased_fragments, + schema, + RebaseErrorHandlingOption::IgnoreError, + ) { + // Rebasing can leave some inefficiencies in some case (particularly when a spread has to be "expanded", see `FragmentSpreadSelection.rebaseOn`), + // so we do a top-level normalization to keep things clean. + rebased_selection = rebased_selection.normalize( + &rebased_type, + &rebased_fragments, + schema, + NormalizeSelectionOption::NormalizeRecursively, + )?; + if NamedFragments::is_selection_set_worth_using(&rebased_selection) { + let fragment = Fragment { + schema: schema.clone(), + name: fragment.name.clone(), + type_condition_position: rebased_type.clone(), + directives: fragment.directives.clone(), + selection_set: rebased_selection, + }; + rebased_fragments.insert(fragment); + } + } + } + } + Ok(rebased_fragments) + } + + /// - Expands all nested fragments + /// - Applies the provided `mapper` to each selection set of the expanded fragments. + /// - Finally, re-fragments the nested fragments. + fn map_to_expanded_selection_sets( + &self, + mut mapper: impl FnMut(&SelectionSet) -> Result, + ) -> Result { + let mut result = NamedFragments::default(); + // Note: `self.fragments` has insertion order topologically sorted. + for fragment in self.fragments.values() { + let expanded_selection_set = fragment.selection_set.expand_all_fragments()?.normalize( + &fragment.type_condition_position, + &Default::default(), + &fragment.schema, + NormalizeSelectionOption::NormalizeRecursively, + )?; + let mapped_selection_set = mapper(&expanded_selection_set)?; + let optimized_selection_set = mapped_selection_set; // TODO: call SelectionSet::optimize (FED-191) + let updated = Fragment { + selection_set: optimized_selection_set, + schema: fragment.schema.clone(), + name: fragment.name.clone(), + type_condition_position: fragment.type_condition_position.clone(), + directives: fragment.directives.clone(), + }; + result.insert(updated); + } + Ok(result) + } + + pub(crate) fn add_typename_field_for_abstract_types_in_named_fragments( + &self, + ) -> Result { + // This method is a bit tricky due to potentially nested fragments. More precisely, suppose that + // we have: + // fragment MyFragment on T { + // a { + // b { + // ...InnerB + // } + // } + // } + // + // fragment InnerB on B { + // __typename + // x + // y + // } + // then if we were to "naively" add `__typename`, the first fragment would end up being: + // fragment MyFragment on T { + // a { + // __typename + // b { + // __typename + // ...InnerX + // } + // } + // } + // but that's not ideal because the inner-most `__typename` is already within `InnerX`. And that + // gets in the way to re-adding fragments (the `SelectionSet.optimize` method) because if we start + // with: + // { + // a { + // __typename + // b { + // __typename + // x + // y + // } + // } + // } + // and add `InnerB` first, we get: + // { + // a { + // __typename + // b { + // ...InnerB + // } + // } + // } + // and it becomes tricky to recognize the "updated-with-typename" version of `MyFragment` now (we "seem" + // to miss a `__typename`). + // + // Anyway, to avoid this issue, what we do is that for every fragment, we: + // 1. expand any nested fragments in its selection. + // 2. add `__typename` where we should in that expanded selection. + // 3. re-optimize all fragments (using the "updated-with-typename" versions). + // which is what `mapToExpandedSelectionSets` gives us. + + if self.is_empty() { + // PORT_NOTE: This was an assertion failure in JS version. But, it's actually ok to + // return unchanged if empty. + return Ok(self.clone()); + } + let updated = self.map_to_expanded_selection_sets(|ss| { + ss.add_typename_field_for_abstract_types( + /*parent_type_if_abstract*/ None, /*fragments*/ &None, + ) + })?; + // PORT_NOTE: The JS version asserts if `updated` is empty or not. But, we really want to + // check the `updated` has the same set of fragments. To avoid performance hit, only the + // size is checked here. + if updated.size() != self.size() { + return Err(FederationError::internal( + "Unexpected change in the number of fragments", + )); + } + Ok(updated) + } +} + +#[derive(Clone)] +pub(crate) struct RebasedFragments { + pub(crate) original_fragments: NamedFragments, + // JS PORT NOTE: In JS implementation values were optional + /// Map key: subgraph name + rebased_fragments: Arc>, +} + +impl RebasedFragments { + pub(crate) fn new(fragments: &NamedFragments) -> Self { + Self { + original_fragments: fragments.clone(), + rebased_fragments: Arc::new(HashMap::new()), + } + } + + pub(crate) fn for_subgraph( + &mut self, + subgraph_name: impl Into, + subgraph_schema: &ValidFederationSchema, + ) -> &NamedFragments { + Arc::make_mut(&mut self.rebased_fragments) + .entry(subgraph_name.into()) + .or_insert_with(|| { + self.original_fragments + .rebase_on(subgraph_schema) + .unwrap_or_default() + }) + } +} + +impl TryFrom<&Operation> for executable::Operation { + type Error = FederationError; + + fn try_from(normalized_operation: &Operation) -> Result { + let operation_type: executable::OperationType = normalized_operation.root_kind.into(); + Ok(Self { + operation_type, + name: normalized_operation.name.clone(), + variables: normalized_operation.variables.deref().clone(), + directives: normalized_operation.directives.deref().clone(), + selection_set: (&normalized_operation.selection_set).try_into()?, + }) + } +} + +impl TryFrom<&Fragment> for executable::Fragment { + type Error = FederationError; + + fn try_from(normalized_fragment: &Fragment) -> Result { + Ok(Self { + name: normalized_fragment.name.clone(), + directives: normalized_fragment.directives.deref().clone(), + selection_set: (&normalized_fragment.selection_set).try_into()?, + }) + } +} + +impl TryFrom<&SelectionSet> for executable::SelectionSet { + type Error = FederationError; + + fn try_from(val: &SelectionSet) -> Result { + let mut flattened = vec![]; + for normalized_selection in val.selections.values() { + let selection: executable::Selection = normalized_selection.try_into()?; + if let executable::Selection::Field(field) = &selection { + if field.name == *INTROSPECTION_TYPENAME_FIELD_NAME && field.alias.is_none() { + // Move unaliased __typename to the start of the selection set. + // This looks nicer, and matches existing tests. + // PORT_NOTE: JS does this in `selectionsInPrintOrder` + flattened.insert(0, selection); + continue; + } + } + flattened.push(selection); + } + Ok(Self { + ty: val.type_position.type_name().clone(), + selections: flattened, + }) + } +} + +impl TryFrom<&Selection> for executable::Selection { + type Error = FederationError; + + fn try_from(val: &Selection) -> Result { + Ok(match val { + Selection::Field(normalized_field_selection) => executable::Selection::Field( + Node::new(normalized_field_selection.deref().try_into()?), + ), + Selection::FragmentSpread(normalized_fragment_spread_selection) => { + executable::Selection::FragmentSpread(Node::new( + normalized_fragment_spread_selection.deref().into(), + )) + } + Selection::InlineFragment(normalized_inline_fragment_selection) => { + executable::Selection::InlineFragment(Node::new( + normalized_inline_fragment_selection.deref().try_into()?, + )) + } + }) + } +} + +impl TryFrom<&Field> for executable::Field { + type Error = FederationError; + + fn try_from(normalized_field: &Field) -> Result { + let definition = normalized_field + .data() + .field_position + .get(normalized_field.data().schema.schema())? + .node + .to_owned(); + let selection_set = executable::SelectionSet { + ty: definition.ty.inner_named_type().clone(), + selections: vec![], + }; + Ok(Self { + definition, + alias: normalized_field.data().alias.to_owned(), + name: normalized_field.data().name().to_owned(), + arguments: normalized_field.data().arguments.deref().to_owned(), + directives: normalized_field.data().directives.deref().to_owned(), + selection_set, + }) + } +} + +impl TryFrom<&FieldSelection> for executable::Field { + type Error = FederationError; + + fn try_from(val: &FieldSelection) -> Result { + let mut field = Self::try_from(&val.field)?; + if let Some(selection_set) = &val.selection_set { + field.selection_set = selection_set.try_into()?; + } + Ok(field) + } +} + +impl TryFrom<&InlineFragment> for executable::InlineFragment { + type Error = FederationError; + + fn try_from(normalized_inline_fragment: &InlineFragment) -> Result { + let type_condition = normalized_inline_fragment + .data() + .type_condition_position + .as_ref() + .map(|pos| pos.type_name().clone()); + let ty = type_condition.clone().unwrap_or_else(|| { + normalized_inline_fragment + .data() + .parent_type_position + .type_name() + .clone() + }); + Ok(Self { + type_condition, + directives: normalized_inline_fragment + .data() + .directives + .deref() + .to_owned(), + selection_set: executable::SelectionSet { + ty, + selections: Vec::new(), + }, + }) + } +} + +impl TryFrom<&InlineFragmentSelection> for executable::InlineFragment { + type Error = FederationError; + + fn try_from(val: &InlineFragmentSelection) -> Result { + Ok(Self { + selection_set: (&val.selection_set).try_into()?, + ..Self::try_from(&val.inline_fragment)? + }) + } +} + +impl From<&FragmentSpreadSelection> for executable::FragmentSpread { + fn from(val: &FragmentSpreadSelection) -> Self { + let normalized_fragment_spread = &val.spread; + Self { + fragment_name: normalized_fragment_spread.data().fragment_name.to_owned(), + directives: normalized_fragment_spread + .data() + .directives + .deref() + .to_owned(), + } + } +} + +impl TryFrom for Valid { + type Error = FederationError; + + fn try_from(value: Operation) -> Result { + let operation = executable::Operation::try_from(&value)?; + let fragments = value + .named_fragments + .fragments + .iter() + .map(|(name, fragment)| { + Ok(( + name.clone(), + Node::new(executable::Fragment::try_from(&**fragment)?), + )) + }) + .collect::, FederationError>>()?; + + let mut document = executable::ExecutableDocument::new(); + document.fragments = fragments; + document.insert_operation(operation); + Ok(document.validate(value.schema.schema())?) + } +} + +impl Display for Operation { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let operation: executable::Operation = match self.try_into() { + Ok(operation) => operation, + Err(_) => return Err(std::fmt::Error), + }; + operation.serialize().fmt(f) + } +} + +impl Display for Fragment { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let fragment: executable::Fragment = match self.try_into() { + Ok(fragment) => fragment, + Err(_) => return Err(std::fmt::Error), + }; + fragment.serialize().fmt(f) + } +} + +impl Display for SelectionSet { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let selection_set: executable::SelectionSet = match self.try_into() { + Ok(selection_set) => selection_set, + Err(_) => return Err(std::fmt::Error), + }; + selection_set.serialize().no_indent().fmt(f) + } +} + +impl Display for Selection { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let selection: executable::Selection = match self.try_into() { + Ok(selection) => selection, + Err(_) => return Err(std::fmt::Error), + }; + selection.serialize().no_indent().fmt(f) + } +} + +impl Display for FieldSelection { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let field: executable::Field = match self.try_into() { + Ok(field) => field, + Err(_) => return Err(std::fmt::Error), + }; + field.serialize().no_indent().fmt(f) + } +} + +impl Display for InlineFragmentSelection { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let inline_fragment: executable::InlineFragment = match self.try_into() { + Ok(inline_fragment) => inline_fragment, + Err(_) => return Err(std::fmt::Error), + }; + inline_fragment.serialize().no_indent().fmt(f) + } +} + +impl Display for FragmentSpreadSelection { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let fragment_spread: executable::FragmentSpread = self.into(); + fragment_spread.serialize().no_indent().fmt(f) + } +} + +impl Display for Field { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + // We create a selection with an empty selection set here, relying on `apollo-rs` to skip + // serializing it when empty. Note we're implicitly relying on the lack of type-checking + // in both `FieldSelection` and `Field` display logic (specifically, we rely on + // them not checking whether it is valid for the selection set to be empty). + self.clone().with_subselection(None).fmt(f) + } +} + +impl Display for InlineFragment { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + // We can't use the same trick we did with `Field`'s display logic, since + // selection sets are non-optional for inline fragment selections. + let data = self.data(); + if let Some(type_name) = &data.type_condition_position { + f.write_str("... on ")?; + f.write_str(type_name.type_name())?; + } else { + f.write_str("...")?; + } + data.directives.serialize().no_indent().fmt(f) + } +} + +fn directives_with_sorted_arguments( + directives: &executable::DirectiveList, +) -> executable::DirectiveList { + let mut directives = directives.clone(); + for directive in &mut directives { + directive + .make_mut() + .arguments + .sort_by(|a1, a2| a1.name.cmp(&a2.name)) + } + directives +} + +fn is_deferred_selection(directives: &executable::DirectiveList) -> bool { + directives.has("defer") +} + +/// Normalizes the selection set of the specified operation. +/// +/// This method applies the following transformations: +/// - Merge selections with the same normalization "key". +/// - Expand fragment spreads into inline fragments. +/// - Remove `__schema` or `__type` introspection fields at all levels, as these shouldn't be +/// handled by query planning. +/// - Hoist fragment spreads/inline fragments into their parents if they have no directives and +/// their parent type matches. +pub(crate) fn normalize_operation( + operation: &executable::Operation, + named_fragments: NamedFragments, + schema: &ValidFederationSchema, + interface_types_with_interface_objects: &IndexSet, +) -> Result { + let mut normalized_selection_set = + SelectionSet::from_selection_set(&operation.selection_set, &named_fragments, schema)?; + normalized_selection_set = normalized_selection_set.expand_all_fragments()?; + normalized_selection_set.optimize_sibling_typenames(interface_types_with_interface_objects)?; + + let normalized_operation = Operation { + schema: schema.clone(), + root_kind: operation.operation_type.into(), + name: operation.name.clone(), + variables: Arc::new(operation.variables.clone()), + directives: Arc::new(operation.directives.clone()), + selection_set: normalized_selection_set, + named_fragments, + }; + Ok(normalized_operation) +} + +// TODO remove once it is available in schema metadata +fn is_interface_object(obj: &ObjectTypeDefinitionPosition, schema: &ValidFederationSchema) -> bool { + if let Ok(intf_obj_directive) = get_federation_spec_definition_from_subgraph(schema) + .and_then(|spec| spec.interface_object_directive(schema)) + { + obj.try_get(schema.schema()) + .is_some_and(|o| o.directives.has(&intf_obj_directive.name)) + } else { + false + } +} + +fn runtime_types_intersect( + type1: &CompositeTypeDefinitionPosition, + type2: &CompositeTypeDefinitionPosition, + schema: &ValidFederationSchema, +) -> bool { + if type1 == type2 { + return true; + } + + if let (Ok(runtimes_1), Ok(runtimes_2)) = ( + schema.possible_runtime_types(type1.clone()), + schema.possible_runtime_types(type2.clone()), + ) { + return runtimes_1.intersection(&runtimes_2).next().is_some(); + } + + false +} + +fn print_possible_runtimes( + composite_type: &CompositeTypeDefinitionPosition, + schema: &ValidFederationSchema, +) -> String { + schema + .possible_runtime_types(composite_type.clone()) + .map_or_else( + |_| "undefined".to_string(), + |runtimes| { + runtimes + .iter() + .map(|r| r.type_name.to_string()) + .collect::>() + .join(", ") + }, + ) +} + +#[cfg(test)] +mod tests { + use apollo_compiler::name; + use apollo_compiler::ExecutableDocument; + use indexmap::IndexSet; + + use super::normalize_operation; + use super::Containment; + use super::ContainmentOptions; + use super::Name; + use super::NamedFragments; + use super::Operation; + use super::Selection; + use super::SelectionKey; + use super::SelectionSet; + use crate::query_graph::graph_path::OpPathElement; + use crate::schema::position::InterfaceTypeDefinitionPosition; + use crate::schema::position::ObjectTypeDefinitionPosition; + use crate::schema::ValidFederationSchema; + use crate::subgraph::Subgraph; + + fn parse_schema_and_operation( + schema_and_operation: &str, + ) -> (ValidFederationSchema, ExecutableDocument) { + let (schema, executable_document) = + apollo_compiler::parse_mixed_validate(schema_and_operation, "document.graphql") + .unwrap(); + let executable_document = executable_document.into_inner(); + let schema = ValidFederationSchema::new(schema).unwrap(); + (schema, executable_document) + } + + fn parse_subgraph(name: &str, schema: &str) -> ValidFederationSchema { + let parsed_schema = + Subgraph::parse_and_expand(name, &format!("https://{name}"), schema).unwrap(); + ValidFederationSchema::new(parsed_schema.schema).unwrap() + } + + #[test] + fn expands_named_fragments() { + let operation_with_named_fragment = r#" +query NamedFragmentQuery { + foo { + id + ...Bar + } +} + +fragment Bar on Foo { + bar + baz +} + +type Query { + foo: Foo +} + +type Foo { + id: ID! + bar: String! + baz: Int +} +"#; + let (schema, mut executable_document) = + parse_schema_and_operation(operation_with_named_fragment); + if let Some(operation) = executable_document + .named_operations + .get_mut("NamedFragmentQuery") + { + let normalized_operation = normalize_operation( + operation, + NamedFragments::new(&executable_document.fragments, &schema), + &schema, + &IndexSet::new(), + ) + .unwrap(); + + let expected = r#"query NamedFragmentQuery { + foo { + id + bar + baz + } +}"#; + let actual = normalized_operation.to_string(); + assert_eq!(expected, actual); + } + } + + #[test] + fn expands_and_deduplicates_fragments() { + let operation_with_named_fragment = r#" +query NestedFragmentQuery { + foo { + ...FirstFragment + ...SecondFragment + } +} + +fragment FirstFragment on Foo { + id + bar + baz +} + +fragment SecondFragment on Foo { + id + bar +} + +type Query { + foo: Foo +} + +type Foo { + id: ID! + bar: String! + baz: String +} +"#; + let (schema, mut executable_document) = + parse_schema_and_operation(operation_with_named_fragment); + if let Some((_, operation)) = executable_document.named_operations.first_mut() { + let normalized_operation = normalize_operation( + operation, + NamedFragments::new(&executable_document.fragments, &schema), + &schema, + &IndexSet::new(), + ) + .unwrap(); + + let expected = r#"query NestedFragmentQuery { + foo { + id + bar + baz + } +}"#; + let actual = normalized_operation.to_string(); + assert_eq!(expected, actual); + } + } + + #[test] + fn can_remove_introspection_selections() { + let operation_with_introspection = r#" +query TestIntrospectionQuery { + __schema { + types { + name + } + } +} + +type Query { + foo: String +} +"#; + let (schema, mut executable_document) = + parse_schema_and_operation(operation_with_introspection); + if let Some(operation) = executable_document + .named_operations + .get_mut("TestIntrospectionQuery") + { + let normalized_operation = normalize_operation( + operation, + NamedFragments::new(&executable_document.fragments, &schema), + &schema, + &IndexSet::new(), + ) + .unwrap(); + + assert!(normalized_operation.selection_set.selections.is_empty()); + } + } + + #[test] + fn merge_same_fields_without_directives() { + let operation_string = r#" +query Test { + t { + v1 + } + t { + v2 + } +} + +type Query { + t: T +} + +type T { + v1: Int + v2: String +} +"#; + let (schema, mut executable_document) = parse_schema_and_operation(operation_string); + if let Some((_, operation)) = executable_document.named_operations.first_mut() { + let normalized_operation = normalize_operation( + operation, + NamedFragments::new(&executable_document.fragments, &schema), + &schema, + &IndexSet::new(), + ) + .unwrap(); + let expected = r#"query Test { + t { + v1 + v2 + } +}"#; + let actual = normalized_operation.to_string(); + assert_eq!(expected, actual); + } else { + panic!("unable to parse document") + } + } + + #[test] + fn merge_same_fields_with_same_directive() { + let operation_with_directives = r#" +query Test($skipIf: Boolean!) { + t @skip(if: $skipIf) { + v1 + } + t @skip(if: $skipIf) { + v2 + } +} + +type Query { + t: T +} + +type T { + v1: Int + v2: String +} +"#; + let (schema, mut executable_document) = + parse_schema_and_operation(operation_with_directives); + if let Some((_, operation)) = executable_document.named_operations.first_mut() { + let normalized_operation = normalize_operation( + operation, + NamedFragments::new(&executable_document.fragments, &schema), + &schema, + &IndexSet::new(), + ) + .unwrap(); + let expected = r#"query Test($skipIf: Boolean!) { + t @skip(if: $skipIf) { + v1 + v2 + } +}"#; + let actual = normalized_operation.to_string(); + assert_eq!(expected, actual); + } else { + panic!("unable to parse document") + } + } + + #[test] + fn merge_same_fields_with_same_directive_but_different_arg_order() { + let operation_with_directives_different_arg_order = r#" +query Test($skipIf: Boolean!) { + t @customSkip(if: $skipIf, label: "foo") { + v1 + } + t @customSkip(label: "foo", if: $skipIf) { + v2 + } +} + +directive @customSkip(if: Boolean!, label: String!) on FIELD | INLINE_FRAGMENT + +type Query { + t: T +} + +type T { + v1: Int + v2: String +} +"#; + let (schema, mut executable_document) = + parse_schema_and_operation(operation_with_directives_different_arg_order); + if let Some((_, operation)) = executable_document.named_operations.first_mut() { + let normalized_operation = normalize_operation( + operation, + NamedFragments::new(&executable_document.fragments, &schema), + &schema, + &IndexSet::new(), + ) + .unwrap(); + let expected = r#"query Test($skipIf: Boolean!) { + t @customSkip(if: $skipIf, label: "foo") { + v1 + v2 + } +}"#; + let actual = normalized_operation.to_string(); + assert_eq!(expected, actual); + } else { + panic!("unable to parse document") + } + } + + #[test] + fn do_not_merge_when_only_one_field_specifies_directive() { + let operation_one_field_with_directives = r#" +query Test($skipIf: Boolean!) { + t { + v1 + } + t @skip(if: $skipIf) { + v2 + } +} + +type Query { + t: T +} + +type T { + v1: Int + v2: String +} +"#; + let (schema, mut executable_document) = + parse_schema_and_operation(operation_one_field_with_directives); + if let Some((_, operation)) = executable_document.named_operations.first_mut() { + let normalized_operation = normalize_operation( + operation, + NamedFragments::new(&executable_document.fragments, &schema), + &schema, + &IndexSet::new(), + ) + .unwrap(); + let expected = r#"query Test($skipIf: Boolean!) { + t { + v1 + } + t @skip(if: $skipIf) { + v2 + } +}"#; + let actual = normalized_operation.to_string(); + assert_eq!(expected, actual); + } else { + panic!("unable to parse document") + } + } + + #[test] + fn do_not_merge_when_fields_have_different_directives() { + let operation_different_directives = r#" +query Test($skip1: Boolean!, $skip2: Boolean!) { + t @skip(if: $skip1) { + v1 + } + t @skip(if: $skip2) { + v2 + } +} + +type Query { + t: T +} + +type T { + v1: Int + v2: String +} +"#; + let (schema, mut executable_document) = + parse_schema_and_operation(operation_different_directives); + if let Some((_, operation)) = executable_document.named_operations.first_mut() { + let normalized_operation = normalize_operation( + operation, + NamedFragments::new(&executable_document.fragments, &schema), + &schema, + &IndexSet::new(), + ) + .unwrap(); + let expected = r#"query Test($skip1: Boolean!, $skip2: Boolean!) { + t @skip(if: $skip1) { + v1 + } + t @skip(if: $skip2) { + v2 + } +}"#; + let actual = normalized_operation.to_string(); + assert_eq!(expected, actual); + } else { + panic!("unable to parse document") + } + } + + #[test] + fn do_not_merge_fields_with_defer_directive() { + let operation_defer_fields = r#" +query Test { + t { + ... @defer { + v1 + } + } + t { + ... @defer { + v2 + } + } +} + +directive @defer(label: String, if: Boolean! = true) on FRAGMENT_SPREAD | INLINE_FRAGMENT + +type Query { + t: T +} + +type T { + v1: Int + v2: String +} +"#; + let (schema, mut executable_document) = parse_schema_and_operation(operation_defer_fields); + if let Some((_, operation)) = executable_document.named_operations.first_mut() { + let normalized_operation = normalize_operation( + operation, + NamedFragments::new(&executable_document.fragments, &schema), + &schema, + &IndexSet::new(), + ) + .unwrap(); + let expected = r#"query Test { + t { + ... @defer { + v1 + } + ... @defer { + v2 + } + } +}"#; + let actual = normalized_operation.to_string(); + assert_eq!(expected, actual); + } else { + panic!("unable to parse document") + } + } + + #[test] + fn merge_nested_field_selections() { + let nested_operation = r#" +query Test { + t { + t1 + ... @defer { + v { + v1 + } + } + } + t { + t1 + t2 + ... @defer { + v { + v2 + } + } + } +} + +directive @defer(label: String, if: Boolean! = true) on FRAGMENT_SPREAD | INLINE_FRAGMENT + +type Query { + t: T +} + +type T { + t1: Int + t2: String + v: V +} + +type V { + v1: Int + v2: String +} +"#; + let (schema, mut executable_document) = parse_schema_and_operation(nested_operation); + if let Some((_, operation)) = executable_document.named_operations.first_mut() { + let normalized_operation = normalize_operation( + operation, + NamedFragments::new(&executable_document.fragments, &schema), + &schema, + &IndexSet::new(), + ) + .unwrap(); + let expected = r#"query Test { + t { + t1 + ... @defer { + v { + v1 + } + } + t2 + ... @defer { + v { + v2 + } + } + } +}"#; + let actual = normalized_operation.to_string(); + assert_eq!(expected, actual); + } else { + panic!("unable to parse document") + } + } + + // + // inline fragments + // + + #[test] + fn merge_same_fragment_without_directives() { + let operation_with_fragments = r#" +query Test { + t { + ... on T { + v1 + } + ... on T { + v2 + } + } +} + +type Query { + t: T +} + +type T { + v1: Int + v2: String +} +"#; + let (schema, mut executable_document) = + parse_schema_and_operation(operation_with_fragments); + if let Some((_, operation)) = executable_document.named_operations.first_mut() { + let normalized_operation = normalize_operation( + operation, + NamedFragments::new(&executable_document.fragments, &schema), + &schema, + &IndexSet::new(), + ) + .unwrap(); + let expected = r#"query Test { + t { + v1 + v2 + } +}"#; + let actual = normalized_operation.to_string(); + assert_eq!(expected, actual); + } else { + panic!("unable to parse document") + } + } + + #[test] + fn merge_same_fragments_with_same_directives() { + let operation_fragments_with_directives = r#" +query Test($skipIf: Boolean!) { + t { + ... on T @skip(if: $skipIf) { + v1 + } + ... on T @skip(if: $skipIf) { + v2 + } + } +} + +type Query { + t: T +} + +type T { + v1: Int + v2: String +} +"#; + let (schema, mut executable_document) = + parse_schema_and_operation(operation_fragments_with_directives); + if let Some((_, operation)) = executable_document.named_operations.first_mut() { + let normalized_operation = normalize_operation( + operation, + NamedFragments::new(&executable_document.fragments, &schema), + &schema, + &IndexSet::new(), + ) + .unwrap(); + let expected = r#"query Test($skipIf: Boolean!) { + t { + ... on T @skip(if: $skipIf) { + v1 + v2 + } + } +}"#; + let actual = normalized_operation.to_string(); + assert_eq!(expected, actual); + } else { + panic!("unable to parse document") + } + } + + #[test] + fn merge_same_fragments_with_same_directive_but_different_arg_order() { + let operation_fragments_with_directives_args_order = r#" +query Test($skipIf: Boolean!) { + t { + ... on T @customSkip(if: $skipIf, label: "foo") { + v1 + } + ... on T @customSkip(label: "foo", if: $skipIf) { + v2 + } + } +} + +directive @customSkip(if: Boolean!, label: String!) on FIELD | INLINE_FRAGMENT + +type Query { + t: T +} + +type T { + v1: Int + v2: String +} +"#; + let (schema, mut executable_document) = + parse_schema_and_operation(operation_fragments_with_directives_args_order); + if let Some((_, operation)) = executable_document.named_operations.first_mut() { + let normalized_operation = normalize_operation( + operation, + NamedFragments::new(&executable_document.fragments, &schema), + &schema, + &IndexSet::new(), + ) + .unwrap(); + let expected = r#"query Test($skipIf: Boolean!) { + t { + ... on T @customSkip(if: $skipIf, label: "foo") { + v1 + v2 + } + } +}"#; + let actual = normalized_operation.to_string(); + assert_eq!(expected, actual); + } else { + panic!("unable to parse document") + } + } + + #[test] + fn do_not_merge_when_only_one_fragment_specifies_directive() { + let operation_one_fragment_with_directive = r#" +query Test($skipIf: Boolean!) { + t { + ... on T { + v1 + } + ... on T @skip(if: $skipIf) { + v2 + } + } +} + +type Query { + t: T +} + +type T { + v1: Int + v2: String +} +"#; + let (schema, mut executable_document) = + parse_schema_and_operation(operation_one_fragment_with_directive); + if let Some((_, operation)) = executable_document.named_operations.first_mut() { + let normalized_operation = normalize_operation( + operation, + NamedFragments::new(&executable_document.fragments, &schema), + &schema, + &IndexSet::new(), + ) + .unwrap(); + let expected = r#"query Test($skipIf: Boolean!) { + t { + v1 + ... on T @skip(if: $skipIf) { + v2 + } + } +}"#; + let actual = normalized_operation.to_string(); + assert_eq!(expected, actual); + } else { + panic!("unable to parse document") + } + } + + #[test] + fn do_not_merge_when_fragments_have_different_directives() { + let operation_fragments_with_different_directive = r#" +query Test($skip1: Boolean!, $skip2: Boolean!) { + t { + ... on T @skip(if: $skip1) { + v1 + } + ... on T @skip(if: $skip2) { + v2 + } + } +} + +type Query { + t: T +} + +type T { + v1: Int + v2: String +} +"#; + let (schema, mut executable_document) = + parse_schema_and_operation(operation_fragments_with_different_directive); + if let Some((_, operation)) = executable_document.named_operations.first_mut() { + let normalized_operation = normalize_operation( + operation, + NamedFragments::new(&executable_document.fragments, &schema), + &schema, + &IndexSet::new(), + ) + .unwrap(); + let expected = r#"query Test($skip1: Boolean!, $skip2: Boolean!) { + t { + ... on T @skip(if: $skip1) { + v1 + } + ... on T @skip(if: $skip2) { + v2 + } + } +}"#; + let actual = normalized_operation.to_string(); + assert_eq!(expected, actual); + } else { + panic!("unable to parse document") + } + } + + #[test] + fn do_not_merge_fragments_with_defer_directive() { + let operation_fragments_with_defer = r#" +query Test { + t { + ... on T @defer { + v1 + } + ... on T @defer { + v2 + } + } +} + +directive @defer(label: String, if: Boolean! = true) on FRAGMENT_SPREAD | INLINE_FRAGMENT + +type Query { + t: T +} + +type T { + v1: Int + v2: String +} +"#; + let (schema, mut executable_document) = + parse_schema_and_operation(operation_fragments_with_defer); + if let Some((_, operation)) = executable_document.named_operations.first_mut() { + let normalized_operation = normalize_operation( + operation, + NamedFragments::new(&executable_document.fragments, &schema), + &schema, + &IndexSet::new(), + ) + .unwrap(); + let expected = r#"query Test { + t { + ... on T @defer { + v1 + } + ... on T @defer { + v2 + } + } +}"#; + let actual = normalized_operation.to_string(); + assert_eq!(expected, actual); + } else { + panic!("unable to parse document") + } + } + + #[test] + fn merge_nested_fragments() { + let operation_nested_fragments = r#" +query Test { + t { + ... on T { + t1 + } + ... on T { + v { + v1 + } + } + } + t { + ... on T { + t1 + t2 + } + ... on T { + v { + v2 + } + } + } +} + +type Query { + t: T +} + +type T { + t1: Int + t2: String + v: V +} + +type V { + v1: Int + v2: String +} +"#; + let (schema, mut executable_document) = + parse_schema_and_operation(operation_nested_fragments); + if let Some((_, operation)) = executable_document.named_operations.first_mut() { + let normalized_operation = normalize_operation( + operation, + NamedFragments::new(&executable_document.fragments, &schema), + &schema, + &IndexSet::new(), + ) + .unwrap(); + let expected = r#"query Test { + t { + t1 + v { + v1 + v2 + } + t2 + } +}"#; + let actual = normalized_operation.to_string(); + assert_eq!(expected, actual); + } else { + panic!("unable to parse document") + } + } + + #[test] + fn removes_sibling_typename() { + let operation_with_typename = r#" +query TestQuery { + foo { + __typename + v1 + v2 + } +} + +type Query { + foo: Foo +} + +type Foo { + v1: ID! + v2: String +} +"#; + let (schema, mut executable_document) = parse_schema_and_operation(operation_with_typename); + if let Some(operation) = executable_document.named_operations.get_mut("TestQuery") { + let normalized_operation = normalize_operation( + operation, + NamedFragments::new(&executable_document.fragments, &schema), + &schema, + &IndexSet::new(), + ) + .unwrap(); + let expected = r#"query TestQuery { + foo { + v1 + v2 + } +}"#; + let actual = normalized_operation.to_string(); + assert_eq!(expected, actual); + } + } + + #[test] + fn keeps_typename_if_no_other_selection() { + let operation_with_single_typename = r#" +query TestQuery { + foo { + __typename + } +} + +type Query { + foo: Foo +} + +type Foo { + v1: ID! + v2: String +} +"#; + let (schema, mut executable_document) = + parse_schema_and_operation(operation_with_single_typename); + if let Some(operation) = executable_document.named_operations.get_mut("TestQuery") { + let normalized_operation = normalize_operation( + operation, + NamedFragments::new(&executable_document.fragments, &schema), + &schema, + &IndexSet::new(), + ) + .unwrap(); + let expected = r#"query TestQuery { + foo { + __typename + } +}"#; + let actual = normalized_operation.to_string(); + assert_eq!(expected, actual); + } + } + + #[test] + fn keeps_typename_for_interface_object() { + let operation_with_intf_object_typename = r#" +query TestQuery { + foo { + __typename + v1 + v2 + } +} + +directive @interfaceObject on OBJECT +directive @key(fields: FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE + +type Query { + foo: Foo +} + +type Foo @interfaceObject @key(fields: "id") { + v1: ID! + v2: String +} + +scalar FieldSet +"#; + let (schema, mut executable_document) = + parse_schema_and_operation(operation_with_intf_object_typename); + if let Some(operation) = executable_document.named_operations.get_mut("TestQuery") { + let mut interface_objects: IndexSet = IndexSet::new(); + interface_objects.insert(InterfaceTypeDefinitionPosition { + type_name: name!("Foo"), + }); + + let normalized_operation = normalize_operation( + operation, + NamedFragments::new(&executable_document.fragments, &schema), + &schema, + &interface_objects, + ) + .unwrap(); + let expected = r#"query TestQuery { + foo { + __typename + v1 + v2 + } +}"#; + let actual = normalized_operation.to_string(); + assert_eq!(expected, actual); + } + } + + // + // REBASE TESTS + // + #[cfg(test)] + mod rebase_tests { + use apollo_compiler::name; + use indexmap::IndexSet; + + use crate::query_plan::operation::normalize_operation; + use crate::query_plan::operation::tests::parse_schema_and_operation; + use crate::query_plan::operation::tests::parse_subgraph; + use crate::query_plan::operation::NamedFragments; + use crate::schema::position::InterfaceTypeDefinitionPosition; + + #[test] + fn skips_unknown_fragment_fields() { + let operation_fragments = r#" +query TestQuery { + t { + ...FragOnT + } +} + +fragment FragOnT on T { + v0 + v1 + v2 + u1 { + v3 + v4 + v5 + } + u2 { + v4 + v5 + } +} + +type Query { + t: T +} + +type T { + v0: Int + v1: Int + v2: Int + u1: U + u2: U +} + +type U { + v3: Int + v4: Int + v5: Int +} +"#; + let (schema, mut executable_document) = parse_schema_and_operation(operation_fragments); + assert!( + !executable_document.fragments.is_empty(), + "operation should have some fragments" + ); + + if let Some(operation) = executable_document.named_operations.get_mut("TestQuery") { + let normalized_operation = normalize_operation( + operation, + NamedFragments::new(&executable_document.fragments, &schema), + &schema, + &IndexSet::new(), + ) + .unwrap(); + + let subgraph_schema = r#"type Query { + _: Int +} + +type T { + v1: Int + u1: U +} + +type U { + v3: Int + v5: Int +}"#; + let subgraph = parse_subgraph("A", subgraph_schema); + let rebased_fragments = normalized_operation.named_fragments.rebase_on(&subgraph); + assert!(rebased_fragments.is_ok()); + let rebased_fragments = rebased_fragments.unwrap(); + assert!(!rebased_fragments.is_empty()); + assert!(rebased_fragments.contains(&name!("FragOnT"))); + let rebased_fragment = rebased_fragments.fragments.get("FragOnT").unwrap(); + + insta::assert_snapshot!(rebased_fragment, @r###" + fragment FragOnT on T { + v1 + u1 { + v3 + v5 + } + } + "###); + } + } + + #[test] + fn skips_unknown_fragment_on_condition() { + let operation_fragments = r#" +query TestQuery { + t { + ...FragOnT + } + u { + ...FragOnU + } +} + +fragment FragOnT on T { + x + y +} + +fragment FragOnU on U { + x + y +} + +type Query { + t: T + u: U +} + +type T { + x: Int + y: Int +} + +type U { + x: Int + y: Int +} +"#; + let (schema, mut executable_document) = parse_schema_and_operation(operation_fragments); + assert!( + !executable_document.fragments.is_empty(), + "operation should have some fragments" + ); + assert_eq!(2, executable_document.fragments.len()); + + if let Some(operation) = executable_document.named_operations.get_mut("TestQuery") { + let normalized_operation = normalize_operation( + operation, + NamedFragments::new(&executable_document.fragments, &schema), + &schema, + &IndexSet::new(), + ) + .unwrap(); + + let subgraph_schema = r#"type Query { + t: T +} + +type T { + x: Int + y: Int +}"#; + let subgraph = parse_subgraph("A", subgraph_schema); + let rebased_fragments = normalized_operation.named_fragments.rebase_on(&subgraph); + assert!(rebased_fragments.is_ok()); + let rebased_fragments = rebased_fragments.unwrap(); + assert!(!rebased_fragments.is_empty()); + assert!(rebased_fragments.contains(&name!("FragOnT"))); + assert!(!rebased_fragments.contains(&name!("FragOnU"))); + let rebased_fragment = rebased_fragments.fragments.get("FragOnT").unwrap(); + + let expected = r#"fragment FragOnT on T { + x + y +}"#; + let actual = rebased_fragment.to_string(); + assert_eq!(actual, expected); + } + } + + #[test] + fn skips_unknown_type_within_fragment() { + let operation_fragments = r#" +query TestQuery { + i { + ...FragOnI + } +} + +fragment FragOnI on I { + id + otherId + ... on T1 { + x + } + ... on T2 { + y + } +} + +type Query { + i: I +} + +interface I { + id: ID! + otherId: ID! +} + +type T1 implements I { + id: ID! + otherId: ID! + x: Int +} + +type T2 implements I { + id: ID! + otherId: ID! + y: Int +} +"#; + let (schema, mut executable_document) = parse_schema_and_operation(operation_fragments); + assert!( + !executable_document.fragments.is_empty(), + "operation should have some fragments" + ); + + if let Some(operation) = executable_document.named_operations.get_mut("TestQuery") { + let normalized_operation = normalize_operation( + operation, + NamedFragments::new(&executable_document.fragments, &schema), + &schema, + &IndexSet::new(), + ) + .unwrap(); + + let subgraph_schema = r#"type Query { + i: I +} + +interface I { + id: ID! +} + +type T2 implements I { + id: ID! + y: Int +} +"#; + let subgraph = parse_subgraph("A", subgraph_schema); + let rebased_fragments = normalized_operation.named_fragments.rebase_on(&subgraph); + assert!(rebased_fragments.is_ok()); + let rebased_fragments = rebased_fragments.unwrap(); + assert!(!rebased_fragments.is_empty()); + assert!(rebased_fragments.contains(&name!("FragOnI"))); + let rebased_fragment = rebased_fragments.fragments.get("FragOnI").unwrap(); + + let expected = r#"fragment FragOnI on I { + id + ... on T2 { + y + } +}"#; + let actual = rebased_fragment.to_string(); + assert_eq!(actual, expected); + } + } + + #[test] + fn skips_typename_on_possible_interface_objects_within_fragment() { + let operation_fragments = r#" +query TestQuery { + i { + ...FragOnI + } +} + +fragment FragOnI on I { + __typename + id + x +} + +type Query { + i: I +} + +interface I { + id: ID! + x: String! +} + +type T implements I { + id: ID! + x: String! +} +"#; + + let (schema, mut executable_document) = parse_schema_and_operation(operation_fragments); + assert!( + !executable_document.fragments.is_empty(), + "operation should have some fragments" + ); + + if let Some(operation) = executable_document.named_operations.get_mut("TestQuery") { + let mut interface_objects: IndexSet = + IndexSet::new(); + interface_objects.insert(InterfaceTypeDefinitionPosition { + type_name: name!("I"), + }); + let normalized_operation = normalize_operation( + operation, + NamedFragments::new(&executable_document.fragments, &schema), + &schema, + &interface_objects, + ) + .unwrap(); + + let subgraph_schema = r#"extend schema @link(url: "https://specs.apollo.dev/link/v1.0") @link(url: "https://specs.apollo.dev/federation/v2.5", import: [{ name: "@interfaceObject" }, { name: "@key" }]) + +directive @link(url: String, as: String, import: [link__Import]) repeatable on SCHEMA + +directive @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE + +directive @interfaceObject on OBJECT + +type Query { + i: I +} + +type I @interfaceObject @key(fields: "id") { + id: ID! + x: String! +} + +scalar link__Import + +scalar federation__FieldSet +"#; + let subgraph = parse_subgraph("A", subgraph_schema); + let rebased_fragments = normalized_operation.named_fragments.rebase_on(&subgraph); + assert!(rebased_fragments.is_ok()); + let rebased_fragments = rebased_fragments.unwrap(); + assert!(!rebased_fragments.is_empty()); + assert!(rebased_fragments.contains(&name!("FragOnI"))); + let rebased_fragment = rebased_fragments.fragments.get("FragOnI").unwrap(); + + let expected = r#"fragment FragOnI on I { + id + x +}"#; + let actual = rebased_fragment.to_string(); + assert_eq!(actual, expected); + } + } + + #[test] + fn skips_fragments_with_trivial_selections() { + let operation_fragments = r#" +query TestQuery { + t { + ...F1 + ...F2 + ...F3 + } +} + +fragment F1 on T { + a + b +} + +fragment F2 on T { + __typename + a + b +} + +fragment F3 on T { + __typename + a + b + c + d +} + +type Query { + t: T +} + +type T { + a: Int + b: Int + c: Int + d: Int +} +"#; + let (schema, mut executable_document) = parse_schema_and_operation(operation_fragments); + assert!( + !executable_document.fragments.is_empty(), + "operation should have some fragments" + ); + + if let Some(operation) = executable_document.named_operations.get_mut("TestQuery") { + let normalized_operation = normalize_operation( + operation, + NamedFragments::new(&executable_document.fragments, &schema), + &schema, + &IndexSet::new(), + ) + .unwrap(); + + let subgraph_schema = r#"type Query { + t: T +} + +type T { + c: Int + d: Int +} +"#; + let subgraph = parse_subgraph("A", subgraph_schema); + let rebased_fragments = normalized_operation.named_fragments.rebase_on(&subgraph); + assert!(rebased_fragments.is_ok()); + let rebased_fragments = rebased_fragments.unwrap(); + // F1 reduces to nothing, and F2 reduces to just __typename so we shouldn't keep them. + assert_eq!(1, rebased_fragments.size()); + assert!(rebased_fragments.contains(&name!("F3"))); + let rebased_fragment = rebased_fragments.fragments.get("F3").unwrap(); + + let expected = r#"fragment F3 on T { + __typename + c + d +}"#; + let actual = rebased_fragment.to_string(); + assert_eq!(actual, expected); + } + } + + #[test] + fn handles_skipped_fragments_within_fragments() { + let operation_fragments = r#" +query TestQuery { + ...TheQuery +} + +fragment TheQuery on Query { + t { + x + ... GetU + } +} + +fragment GetU on T { + u { + y + z + } +} + +type Query { + t: T +} + +type T { + x: Int + u: U +} + +type U { + y: Int + z: Int +} +"#; + let (schema, mut executable_document) = parse_schema_and_operation(operation_fragments); + assert!( + !executable_document.fragments.is_empty(), + "operation should have some fragments" + ); + + if let Some(operation) = executable_document.named_operations.get_mut("TestQuery") { + let normalized_operation = normalize_operation( + operation, + NamedFragments::new(&executable_document.fragments, &schema), + &schema, + &IndexSet::new(), + ) + .unwrap(); + + let subgraph_schema = r#"type Query { + t: T +} + +type T { + x: Int +}"#; + let subgraph = parse_subgraph("A", subgraph_schema); + let rebased_fragments = normalized_operation.named_fragments.rebase_on(&subgraph); + assert!(rebased_fragments.is_ok()); + let rebased_fragments = rebased_fragments.unwrap(); + // F1 reduces to nothing, and F2 reduces to just __typename so we shouldn't keep them. + assert_eq!(1, rebased_fragments.size()); + assert!(rebased_fragments.contains(&name!("TheQuery"))); + let rebased_fragment = rebased_fragments.fragments.get("TheQuery").unwrap(); + + let expected = r#"fragment TheQuery on Query { + t { + x + } +}"#; + let actual = rebased_fragment.to_string(); + assert_eq!(actual, expected); + } + } + + #[test] + fn handles_subtypes_within_subgraphs() { + let operation_fragments = r#" +query TestQuery { + ...TQuery +} + +fragment TQuery on Query { + t { + x + y + ... on T { + z + } + } +} + +type Query { + t: I +} + +interface I { + x: Int + y: Int +} + +type T implements I { + x: Int + y: Int + z: Int +} +"#; + let (schema, mut executable_document) = parse_schema_and_operation(operation_fragments); + assert!( + !executable_document.fragments.is_empty(), + "operation should have some fragments" + ); + + if let Some(operation) = executable_document.named_operations.get_mut("TestQuery") { + let normalized_operation = normalize_operation( + operation, + NamedFragments::new(&executable_document.fragments, &schema), + &schema, + &IndexSet::new(), + ) + .unwrap(); + + let subgraph_schema = r#"type Query { + t: T +} + +type T { + x: Int + y: Int + z: Int +} +"#; + + let subgraph = parse_subgraph("A", subgraph_schema); + let rebased_fragments = normalized_operation.named_fragments.rebase_on(&subgraph); + assert!(rebased_fragments.is_ok()); + let rebased_fragments = rebased_fragments.unwrap(); + // F1 reduces to nothing, and F2 reduces to just __typename so we shouldn't keep them. + assert_eq!(1, rebased_fragments.size()); + assert!(rebased_fragments.contains(&name!("TQuery"))); + let rebased_fragment = rebased_fragments.fragments.get("TQuery").unwrap(); + + let expected = r#"fragment TQuery on Query { + t { + x + y + z + } +}"#; + let actual = rebased_fragment.to_string(); + assert_eq!(actual, expected); + } + } + } + + fn containment_custom(left: &str, right: &str, ignore_missing_typename: bool) -> Containment { + let schema = apollo_compiler::Schema::parse_and_validate( + r#" + directive @defer(label: String, if: Boolean! = true) on FRAGMENT_SPREAD | INLINE_FRAGMENT + + interface Intf { + intfField: Int + } + type HasA implements Intf { + a: Boolean + intfField: Int + } + type Nested { + a: Int + b: Int + c: Int + } + input Input { + recur: Input + f: Boolean + g: Boolean + h: Boolean + } + type Query { + a: Int + b: Int + c: Int + object: Nested + intf: Intf + arg(a: Int, b: Int, c: Int, d: Input): Int + } + "#, + "schema.graphql", + ) + .unwrap(); + let schema = ValidFederationSchema::new(schema).unwrap(); + let left = Operation::parse(schema.clone(), left, "left.graphql", None).unwrap(); + let right = Operation::parse(schema.clone(), right, "right.graphql", None).unwrap(); + + left.selection_set.containment( + &right.selection_set, + ContainmentOptions { + ignore_missing_typename, + }, + ) + } + + fn containment(left: &str, right: &str) -> Containment { + containment_custom(left, right, false) + } + + #[test] + fn selection_set_contains() { + assert_eq!(containment("{ a }", "{ a }"), Containment::Equal); + assert_eq!(containment("{ a b }", "{ b a }"), Containment::Equal); + assert_eq!( + containment("{ arg(a: 1) }", "{ arg(a: 2) }"), + Containment::NotContained + ); + assert_eq!( + containment("{ arg(a: 1) }", "{ arg(b: 1) }"), + Containment::NotContained + ); + assert_eq!( + containment("{ arg(a: 1) }", "{ arg(a: 1) }"), + Containment::Equal + ); + assert_eq!( + containment("{ arg(a: 1, b: 1) }", "{ arg(b: 1 a: 1) }"), + Containment::Equal + ); + assert_eq!( + containment("{ arg(a: 1) }", "{ arg(a: 1) }"), + Containment::Equal + ); + assert_eq!( + containment( + "{ arg(d: { f: true, g: true }) }", + "{ arg(d: { f: true }) }" + ), + Containment::NotContained + ); + assert_eq!( + containment( + "{ arg(d: { recur: { f: true } g: true h: false }) }", + "{ arg(d: { h: false recur: {f: true} g: true }) }" + ), + Containment::Equal + ); + assert_eq!( + containment("{ arg @skip(if: true) }", "{ arg @skip(if: true) }"), + Containment::Equal + ); + assert_eq!( + containment("{ arg @skip(if: true) }", "{ arg @skip(if: false) }"), + Containment::NotContained + ); + assert_eq!( + containment("{ ... @defer { arg } }", "{ ... @defer { arg } }"), + Containment::NotContained, + "@defer selections never contain each other" + ); + assert_eq!( + containment("{ a b c }", "{ b a }"), + Containment::StrictlyContained + ); + assert_eq!( + containment("{ a b }", "{ b c a }"), + Containment::NotContained + ); + assert_eq!(containment("{ a }", "{ b }"), Containment::NotContained); + assert_eq!( + containment("{ object { a } }", "{ object { b a } }"), + Containment::NotContained + ); + + assert_eq!( + containment("{ ... { a } }", "{ ... { a } }"), + Containment::Equal + ); + assert_eq!( + containment( + "{ intf { ... on HasA { a } } }", + "{ intf { ... on HasA { a } } }", + ), + Containment::Equal + ); + // These select the same things, but containment also counts fragment namedness + assert_eq!( + containment( + "{ intf { ... on HasA { a } } }", + "{ intf { ...named } } fragment named on HasA { a }", + ), + Containment::NotContained + ); + assert_eq!( + containment( + "{ intf { ...named } } fragment named on HasA { a intfField }", + "{ intf { ...named } } fragment named on HasA { a }", + ), + Containment::StrictlyContained + ); + assert_eq!( + containment( + "{ intf { ...named } } fragment named on HasA { a }", + "{ intf { ...named } } fragment named on HasA { a intfField }", + ), + Containment::NotContained + ); + } + + #[test] + fn selection_set_contains_missing_typename() { + assert_eq!( + containment_custom("{ a }", "{ a __typename }", true), + Containment::Equal + ); + assert_eq!( + containment_custom("{ a b }", "{ b a __typename }", true), + Containment::Equal + ); + assert_eq!( + containment_custom("{ a b }", "{ b __typename }", true), + Containment::StrictlyContained + ); + assert_eq!( + containment_custom("{ object { a b } }", "{ object { b __typename } }", true), + Containment::StrictlyContained + ); + assert_eq!( + containment_custom( + "{ intf { intfField __typename } }", + "{ intf { intfField } }", + true + ), + Containment::StrictlyContained, + ); + assert_eq!( + containment_custom( + "{ intf { intfField __typename } }", + "{ intf { intfField __typename } }", + true + ), + Containment::Equal, + ); + } + + /// This regression-tests an assumption from + /// https://github.com/apollographql/federation-next/pull/290#discussion_r1587200664 + #[test] + fn converting_operation_types() { + let schema = apollo_compiler::Schema::parse_and_validate( + r#" + interface Intf { + intfField: Int + } + type HasA implements Intf { + a: Boolean + intfField: Int + } + type Nested { + a: Int + b: Int + c: Int + } + type Query { + a: Int + b: Int + c: Int + object: Nested + intf: Intf + } + "#, + "schema.graphql", + ) + .unwrap(); + let schema = ValidFederationSchema::new(schema).unwrap(); + insta::assert_snapshot!(Operation::parse( + schema.clone(), + r#" + { + intf { + ... on HasA { a } + ... frag + } + } + fragment frag on HasA { intfField } + "#, + "operation.graphql", + None, + ) + .unwrap(), @r###" + { + intf { + ... on HasA { + a + } + ...frag + } + } + "###); + } + + fn contains_field(ss: &SelectionSet, field_name: Name) -> bool { + ss.selections.contains_key(&SelectionKey::Field { + response_name: field_name, + directives: Default::default(), + }) + } + + fn is_named_field(sk: &SelectionKey, name: Name) -> bool { + matches!(sk, + SelectionKey::Field { response_name, directives: _ } + if *response_name == name) + } + + fn get_value_at_path<'a>(ss: &'a SelectionSet, path: &[Name]) -> Option<&'a Selection> { + let Some((first, rest)) = path.split_first() else { + // Error: empty path + return None; + }; + let result = ss.selections.get(&SelectionKey::Field { + response_name: (*first).clone(), + directives: Default::default(), + }); + let Some(value) = result else { + // Error: No matching field found. + return None; + }; + if rest.is_empty() { + // Base case => We are done. + Some(value) + } else { + // Recursive case + match value.selection_set().unwrap() { + None => None, // Error: Sub-selection expected, but not found. + Some(ss) => get_value_at_path(ss, rest), + } + } + } + + #[cfg(test)] + mod make_selection_tests { + use super::super::*; + use super::*; + + const SAMPLE_OPERATION_DOC: &str = r#" + type Query { + foo: Foo! + } + + type Foo { + a: Int! + b: Int! + c: Int! + } + + query TestQuery { + foo { + a + b + c + } + } + "#; + + // Tests if `make_selection`'s subselection ordering is preserved. + #[test] + fn test_make_selection_order() { + let (schema, executable_document) = parse_schema_and_operation(SAMPLE_OPERATION_DOC); + let normalized_operation = normalize_operation( + executable_document.get_operation(None).unwrap(), + Default::default(), + &schema, + &Default::default(), + ) + .unwrap(); + + let foo = get_value_at_path(&normalized_operation.selection_set, &[name!("foo")]) + .expect("foo should exist"); + assert_eq!(foo.to_string(), "foo { a b c }"); + + // Create a new foo with a different selection order using `make_selection`. + let clone_selection_at_path = |base: &Selection, path: &[Name]| { + let base_selection_set = base.selection_set().unwrap().unwrap(); + let selection = + get_value_at_path(base_selection_set, path).expect("path should exist"); + let subselections = SelectionSet::from_selection( + base_selection_set.type_position.clone(), + selection.clone(), + ); + Selection::from_element(base.element().unwrap(), Some(subselections)).unwrap() + }; + + let foo_with_a = clone_selection_at_path(foo, &[name!("a")]); + let foo_with_b = clone_selection_at_path(foo, &[name!("b")]); + let foo_with_c = clone_selection_at_path(foo, &[name!("c")]); + let new_selection = SelectionSet::make_selection( + &schema, + &foo.element().unwrap().parent_type_position(), + [foo_with_c, foo_with_b, foo_with_a].iter(), + ) + .unwrap(); + // Make sure the ordering of c, b and a is preserved. + assert_eq!(new_selection.to_string(), "foo { c b a }"); + } + } + + #[cfg(test)] + mod lazy_map_tests { + use super::super::*; + use super::*; + + // recursive filter implementation using `lazy_map` + fn filter_rec( + ss: &SelectionSet, + pred: &impl Fn(&Selection) -> bool, + ) -> Result { + ss.lazy_map(|s| { + if !pred(s) { + return Ok(SelectionMapperReturn::None); + } + match s.selection_set()? { + // Base case: leaf field + None => Ok(s.clone().into()), + + // Recursive case: non-leaf field + Some(inner_ss) => { + let updated_ss = filter_rec(inner_ss, pred).map(Some)?; + // see if `updated_ss` is an non-empty selection set. + if matches!(updated_ss, Some(ref sub_ss) if !sub_ss.is_empty()) { + s.with_updated_selection_set(updated_ss).map(|ss| ss.into()) + } else { + Ok(SelectionMapperReturn::None) + } + } + } + }) + } + + const SAMPLE_OPERATION_DOC: &str = r#" + type Query { + foo: Foo! + some_int: Int! + foo2: Foo! + } + + type Foo { + id: ID! + bar: String! + baz: Int + } + + query TestQuery { + foo { + id + bar + }, + some_int + foo2 { + bar + } + } + "#; + + // Tests `lazy_map` via `filter_rec` function. + #[test] + fn test_lazy_map() { + let (schema, executable_document) = parse_schema_and_operation(SAMPLE_OPERATION_DOC); + let normalized_operation = normalize_operation( + executable_document.get_operation(None).unwrap(), + Default::default(), + &schema, + &Default::default(), + ) + .unwrap(); + + let selection_set = normalized_operation.selection_set; + + // Select none + let select_none = filter_rec(&selection_set, &|_| false).unwrap(); + assert!(select_none.is_empty()); + + // Select all + let select_all = filter_rec(&selection_set, &|_| true).unwrap(); + assert!(select_all == selection_set); + + // Remove `foo` + let remove_foo = + filter_rec(&selection_set, &|s| !is_named_field(&s.key(), name!("foo"))).unwrap(); + assert!(contains_field(&remove_foo, name!("some_int"))); + assert!(contains_field(&remove_foo, name!("foo2"))); + assert!(!contains_field(&remove_foo, name!("foo"))); + + // Remove `bar` + let remove_bar = + filter_rec(&selection_set, &|s| !is_named_field(&s.key(), name!("bar"))).unwrap(); + // "foo2" should be removed, since it has no sub-selections left. + assert!(!contains_field(&remove_bar, name!("foo2"))); + } + + fn add_typename_if( + ss: &SelectionSet, + pred: &impl Fn(&Selection) -> bool, + ) -> Result { + ss.lazy_map(|s| { + let to_add_typename = pred(s); + let updated = s.map_selection_set(|ss| add_typename_if(ss, pred).map(Some))?; + if !to_add_typename { + return Ok(updated.into()); + } + + let parent_type_pos = s.element()?.parent_type_position(); + // "__typename" field + let field_element = + Field::new_introspection_typename(s.schema(), &parent_type_pos, None); + let typename_selection = + Selection::from_element(field_element.into(), /*subselection*/ None)?; + // return `updated` and `typename_selection` + Ok([updated, typename_selection].into_iter().collect()) + }) + } + + // Tests `lazy_map` via `add_typename_if` function. + #[test] + fn test_lazy_map2() { + let (schema, executable_document) = parse_schema_and_operation(SAMPLE_OPERATION_DOC); + let normalized_operation = normalize_operation( + executable_document.get_operation(None).unwrap(), + Default::default(), + &schema, + &Default::default(), + ) + .unwrap(); + + let selection_set = normalized_operation.selection_set; + + // Add __typename next to any "id" field. + let result = + add_typename_if(&selection_set, &|s| is_named_field(&s.key(), name!("id"))) + .unwrap(); + + // The top level won't have __typename, since it doesn't have "id". + assert!(!contains_field(&result, name!("__typename"))); + + // Check if "foo" has "__typename". + get_value_at_path(&result, &[name!("foo"), name!("__typename")]) + .expect("foo.__typename should exist"); + } + } + + fn field_element( + schema: &ValidFederationSchema, + object: apollo_compiler::schema::Name, + field: apollo_compiler::schema::Name, + ) -> OpPathElement { + OpPathElement::Field(super::Field::new(super::FieldData { + schema: schema.clone(), + field_position: ObjectTypeDefinitionPosition::new(object) + .field(field) + .into(), + alias: None, + arguments: Default::default(), + directives: Default::default(), + sibling_typename: None, + })) + } + + const ADD_AT_PATH_TEST_SCHEMA: &str = r#" + type A { b: B } + type B { c: C } + type C implements X { + d: Int + e(arg: Int): Int + } + type D implements X { + d: Int + e: Boolean + } + + interface X { + d: Int + } + type Query { + a: A + something: Boolean! + scalar: String + withArg(arg: Int): X + } + "#; + + #[test] + fn add_at_path_merge_scalar_fields() { + let schema = + apollo_compiler::Schema::parse_and_validate(ADD_AT_PATH_TEST_SCHEMA, "schema.graphql") + .unwrap(); + let schema = ValidFederationSchema::new(schema).unwrap(); + + let mut selection_set = SelectionSet::empty( + schema.clone(), + ObjectTypeDefinitionPosition::new(name!("Query")).into(), + ); + + selection_set + .add_at_path( + &[field_element(&schema, name!("Query"), name!("scalar")).into()], + None, + ) + .unwrap(); + + selection_set + .add_at_path( + &[field_element(&schema, name!("Query"), name!("scalar")).into()], + None, + ) + .unwrap(); + + insta::assert_snapshot!(selection_set, @r#"{ scalar }"#); + } + + #[test] + fn add_at_path_merge_subselections() { + let schema = + apollo_compiler::Schema::parse_and_validate(ADD_AT_PATH_TEST_SCHEMA, "schema.graphql") + .unwrap(); + let schema = ValidFederationSchema::new(schema).unwrap(); + + let mut selection_set = SelectionSet::empty( + schema.clone(), + ObjectTypeDefinitionPosition::new(name!("Query")).into(), + ); + + let path_to_c = [ + field_element(&schema, name!("Query"), name!("a")).into(), + field_element(&schema, name!("A"), name!("b")).into(), + field_element(&schema, name!("B"), name!("c")).into(), + ]; + + selection_set + .add_at_path( + &path_to_c, + Some( + &SelectionSet::parse( + schema.clone(), + ObjectTypeDefinitionPosition::new(name!("C")).into(), + "d", + ) + .unwrap() + .into(), + ), + ) + .unwrap(); + selection_set + .add_at_path( + &path_to_c, + Some( + &SelectionSet::parse( + schema.clone(), + ObjectTypeDefinitionPosition::new(name!("C")).into(), + "e(arg: 1)", + ) + .unwrap() + .into(), + ), + ) + .unwrap(); + + insta::assert_snapshot!(selection_set, @r#"{ a { b { c { d e(arg: 1) } } } }"#); + } + + // TODO: `.add_at_path` should collapse unnecessary fragments + #[test] + #[ignore] + fn add_at_path_collapses_unnecessary_fragments() { + let schema = + apollo_compiler::Schema::parse_and_validate(ADD_AT_PATH_TEST_SCHEMA, "schema.graphql") + .unwrap(); + let schema = ValidFederationSchema::new(schema).unwrap(); + + let mut selection_set = SelectionSet::empty( + schema.clone(), + ObjectTypeDefinitionPosition::new(name!("Query")).into(), + ); + selection_set + .add_at_path( + &[ + field_element(&schema, name!("Query"), name!("a")).into(), + field_element(&schema, name!("A"), name!("b")).into(), + field_element(&schema, name!("B"), name!("c")).into(), + ], + Some( + &SelectionSet::parse( + schema.clone(), + InterfaceTypeDefinitionPosition::new(name!("X")).into(), + "... on C { d }", + ) + .unwrap() + .into(), + ), + ) + .unwrap(); + + insta::assert_snapshot!(selection_set, @r#"{ a { b { c { d } } } }"#); + } +} diff --git a/apollo-federation/src/query_plan/query_planner.rs b/apollo-federation/src/query_plan/query_planner.rs new file mode 100644 index 0000000000..55d6f225c7 --- /dev/null +++ b/apollo-federation/src/query_plan/query_planner.rs @@ -0,0 +1,982 @@ +use std::num::NonZeroU32; +use std::sync::Arc; + +use apollo_compiler::schema::ExtendedType; +use apollo_compiler::schema::Name; +use apollo_compiler::validation::Valid; +use apollo_compiler::ExecutableDocument; +use apollo_compiler::NodeStr; +use indexmap::IndexMap; +use indexmap::IndexSet; + +use crate::error::FederationError; +use crate::error::SingleFederationError; +use crate::link::federation_spec_definition::FederationSpecDefinition; +use crate::link::federation_spec_definition::FEDERATION_INTERFACEOBJECT_DIRECTIVE_NAME_IN_SPEC; +use crate::link::spec::Identity; +use crate::query_graph::build_federated_query_graph; +use crate::query_graph::QueryGraph; +use crate::query_plan::fetch_dependency_graph::FetchDependencyGraph; +use crate::query_plan::fetch_dependency_graph_processor::FetchDependencyGraphProcessor; +use crate::query_plan::fetch_dependency_graph_processor::FetchDependencyGraphToCostProcessor; +use crate::query_plan::fetch_dependency_graph_processor::FetchDependencyGraphToQueryPlanProcessor; +use crate::query_plan::operation::normalize_operation; +use crate::query_plan::operation::NamedFragments; +use crate::query_plan::operation::NormalizedDefer; +use crate::query_plan::operation::RebasedFragments; +use crate::query_plan::operation::SelectionSet; +use crate::query_plan::query_planning_traversal::BestQueryPlanInfo; +use crate::query_plan::query_planning_traversal::QueryPlanningParameters; +use crate::query_plan::query_planning_traversal::QueryPlanningTraversal; +use crate::query_plan::FetchNode; +use crate::query_plan::PlanNode; +use crate::query_plan::QueryPlan; +use crate::query_plan::SequenceNode; +use crate::query_plan::TopLevelPlanNode; +use crate::schema::position::AbstractTypeDefinitionPosition; +use crate::schema::position::InterfaceTypeDefinitionPosition; +use crate::schema::position::ObjectTypeDefinitionPosition; +use crate::schema::position::SchemaRootDefinitionKind; +use crate::schema::position::TypeDefinitionPosition; +use crate::schema::ValidFederationSchema; +use crate::ApiSchemaOptions; +use crate::Supergraph; + +#[derive(Debug, Clone, Hash)] +pub struct QueryPlannerConfig { + /// Whether the query planner should try to reused the named fragments of the planned query in + /// subgraph fetches. + /// + /// This is often a good idea as it can prevent very large subgraph queries in some cases (named + /// fragments can make some relatively small queries (using said fragments) expand to a very large + /// query if all the spreads are inline). However, due to architecture of the query planner, this + /// optimization is done as an additional pass on the subgraph queries of the generated plan and + /// can thus increase the latency of building a plan. As long as query plans are sufficiently + /// cached, this should not be a problem, which is why this option is enabled by default, but if + /// the distribution of inbound queries prevents efficient caching of query plans, this may become + /// an undesirable trade-off and can be disabled in that case. + /// + /// Defaults to true. + pub reuse_query_fragments: bool, + + /// Whether to run GraphQL validation against the extracted subgraph schemas. Recommended in + /// non-production settings or when debugging. + /// + /// Defaults to false. + pub subgraph_graphql_validation: bool, + + // Side-note: implemented as an object instead of single boolean because we expect to add more + // to this soon enough. In particular, once defer-passthrough to subgraphs is implemented, the + // idea would be to add a new `passthrough_subgraphs` option that is the list of subgraphs to + // which we can pass-through some @defer (and it would be empty by default). Similarly, once we + // support @stream, grouping the options here will make sense too. + pub incremental_delivery: QueryPlanIncrementalDeliveryConfig, + + /// A sub-set of configurations that are meant for debugging or testing. All the configurations + /// in this sub-set are provided without guarantees of stability (they may be dangerous) or + /// continued support (they may be removed without warning). + pub debug: QueryPlannerDebugConfig, +} + +impl Default for QueryPlannerConfig { + fn default() -> Self { + Self { + reuse_query_fragments: true, + subgraph_graphql_validation: false, + incremental_delivery: Default::default(), + debug: Default::default(), + } + } +} + +#[derive(Debug, Clone, Default, Hash)] +pub struct QueryPlanIncrementalDeliveryConfig { + /// Enables @defer support by the query planner. + /// + /// If set, then the query plan for queries having some @defer will contains some `DeferNode` + /// (see `query_plan/mod.rs`). + /// + /// Defaults to false (meaning that the @defer are ignored). + pub enable_defer: bool, +} + +#[derive(Debug, Clone, Hash)] +pub struct QueryPlannerDebugConfig { + /// If used and the supergraph is built from a single subgraph, then user queries do not go + /// through the normal query planning and instead a fetch to the one subgraph is built directly + /// from the input query. + pub bypass_planner_for_single_subgraph: bool, + + /// Query planning is an exploratory process. Depending on the specificities and feature used by + /// subgraphs, there could exist may different theoretical valid (if not always efficient) plans + /// for a given query, and at a high level, the query planner generates those possible choices, + /// evaluates them, and return the best one. In some complex cases however, the number of + /// theoretically possible plans can be very large, and to keep query planning time acceptable, + /// the query planner caps the maximum number of plans it evaluates. This config allows to + /// configure that cap. Note if planning a query hits that cap, then the planner will still + /// always return a "correct" plan, but it may not return _the_ optimal one, so this config can + /// be considered a trade-off between the worst-time for query planning computation processing, + /// and the risk of having non-optimal query plans (impacting query runtimes). + /// + /// This value currently defaults to 10000, but this default is considered an implementation + /// detail and is subject to change. We do not recommend setting this value unless it is to + /// debug a specific issue (with unexpectedly slow query planning for instance). Remember that + /// setting this value too low can negatively affect query runtime (due to the use of + /// sub-optimal query plans). + // TODO: should there additionally be a max_evaluated_cost? + pub max_evaluated_plans: NonZeroU32, + + /// Before creating query plans, for each path of fields in the query we compute all the + /// possible options to traverse that path via the subgraphs. Multiple options can arise because + /// fields in the path can be provided by multiple subgraphs, and abstract types (i.e. unions + /// and interfaces) returned by fields sometimes require the query planner to traverse through + /// each constituent object type. The number of options generated in this computation can grow + /// large if the schema or query are sufficiently complex, and that will increase the time spent + /// planning. + /// + /// This config allows specifying a per-path limit to the number of options considered. If any + /// path's options exceeds this limit, query planning will abort and the operation will fail. + /// + /// The default value is None, which specifies no limit. + pub paths_limit: Option, +} + +impl Default for QueryPlannerDebugConfig { + fn default() -> Self { + Self { + bypass_planner_for_single_subgraph: false, + max_evaluated_plans: NonZeroU32::new(10_000).unwrap(), + paths_limit: None, + } + } +} + +// PORT_NOTE: renamed from PlanningStatistics in the JS codebase. +#[derive(Debug, Default, Clone)] +pub struct QueryPlanningStatistics { + pub evaluated_plan_count: usize, +} + +impl QueryPlannerConfig { + /// Panics if options are used together in unsupported ways. + fn assert_valid(&self) { + if self.incremental_delivery.enable_defer { + assert!(!self.debug.bypass_planner_for_single_subgraph, "Cannot use the `debug.bypass_planner_for_single_subgraph` query planner option when @defer support is enabled"); + } + } +} + +pub struct QueryPlanner { + config: QueryPlannerConfig, + federated_query_graph: Arc, + supergraph_schema: ValidFederationSchema, + api_schema: ValidFederationSchema, + subgraph_federation_spec_definitions: Arc>, + /// A set of the names of interface types for which at least one subgraph use an + /// @interfaceObject to abstract that interface. + interface_types_with_interface_objects: IndexSet, + /// A set of the names of interface or union types that have inconsistent "runtime types" across + /// subgraphs. + // PORT_NOTE: Named `inconsistentAbstractTypesRuntimes` in the JS codebase, which was slightly + // confusing. + abstract_types_with_inconsistent_runtime_types: IndexSet, +} + +impl QueryPlanner { + pub fn new( + supergraph: &Supergraph, + config: QueryPlannerConfig, + ) -> Result { + config.assert_valid(); + + let supergraph_schema = supergraph.schema.clone(); + let api_schema = supergraph.to_api_schema(ApiSchemaOptions { + include_defer: config.incremental_delivery.enable_defer, + ..Default::default() + })?; + let query_graph = build_federated_query_graph( + supergraph_schema.clone(), + api_schema.clone(), + Some(true), + Some(true), + )?; + + let metadata = supergraph_schema.metadata().unwrap(); + + let federation_link = metadata.for_identity(&Identity::federation_identity()); + let interface_object_directive = + federation_link.map_or(FEDERATION_INTERFACEOBJECT_DIRECTIVE_NAME_IN_SPEC, |link| { + link.directive_name_in_schema(&FEDERATION_INTERFACEOBJECT_DIRECTIVE_NAME_IN_SPEC) + }); + + let is_interface_object = + |ty: &ExtendedType| ty.is_object() && ty.directives().has(&interface_object_directive); + + let interface_types_with_interface_objects = supergraph + .schema + .get_types() + .filter_map(|position| match position { + TypeDefinitionPosition::Interface(interface_position) => Some(interface_position), + _ => None, + }) + .filter(|position| { + query_graph.sources().any(|(_name, schema)| { + schema + .schema() + .types + .get(&position.type_name) + .is_some_and(is_interface_object) + }) + }) + .collect::>(); + + let is_inconsistent = |position: AbstractTypeDefinitionPosition| { + let mut sources = query_graph.sources().filter_map(|(_name, subgraph)| { + match subgraph.try_get_type(position.type_name().clone())? { + // This is only called for type names that are abstract in the supergraph, so it + // can only be an object in a subgraph if it is an `@interfaceObject`. And as `@interfaceObject`s + // "stand-in" for all possible runtime types, they don't create inconsistencies by themselves + // and we can ignore them. + TypeDefinitionPosition::Object(_) => None, + TypeDefinitionPosition::Interface(interface) => Some( + subgraph + .referencers() + .get_interface_type(&interface.type_name) + .ok()? + .object_types + .clone(), + ), + TypeDefinitionPosition::Union(union_) => Some( + union_ + .try_get(subgraph.schema())? + .members + .iter() + .map(|member| ObjectTypeDefinitionPosition::new(member.name.clone())) + .collect(), + ), + _ => None, + } + }); + + let Some(expected_runtimes) = sources.next() else { + return false; + }; + sources.all(|runtimes| runtimes == expected_runtimes) + }; + + let abstract_types_with_inconsistent_runtime_types = supergraph + .schema + .get_types() + .filter_map(|position| AbstractTypeDefinitionPosition::try_from(position).ok()) + .filter(|position| is_inconsistent(position.clone())) + .collect::>(); + + // PORT_NOTE: JS prepares a map of override conditions here, which is + // a map where the keys are all `@join__field(overrideLabel:)` argument values + // and the values are all initialised to `false`. Instead of doing that, we should + // be able to use a Set where presence means `true` and absence means `false`. + + Ok(Self { + config, + federated_query_graph: Arc::new(query_graph), + supergraph_schema, + api_schema, + // TODO(@goto-bus-stop): not sure how this is going to be used, + // keeping empty for the moment + subgraph_federation_spec_definitions: Default::default(), + interface_types_with_interface_objects, + abstract_types_with_inconsistent_runtime_types, + }) + } + + pub fn subgraph_schemas(&self) -> &IndexMap { + &self.federated_query_graph.sources + } + + // PORT_NOTE: this receives an `Operation` object in JS which is a concept that doesn't exist in apollo-rs. + pub fn build_query_plan( + &self, + document: &Valid, + operation_name: Option, + ) -> Result { + let operation = document + .get_operation(operation_name.as_ref().map(|name| name.as_str())) + // TODO(@goto-bus-stop) this is not an internal error, but a user error + .map_err(|_| FederationError::internal("requested operation does not exist"))?; + + if operation.selection_set.selections.is_empty() { + // This should never happen because `operation` comes from a known-valid document. + return Err(SingleFederationError::InvalidGraphQL { + message: "Invalid operation: empty selection set".to_string(), + } + .into()); + } + + let is_subscription = operation.is_subscription(); + + let statistics = QueryPlanningStatistics { + evaluated_plan_count: 0, + }; + + if self.config.debug.bypass_planner_for_single_subgraph { + // A federated query graph always have 1 more sources than there is subgraph, because the root vertices + // belong to no subgraphs and use a special source named '_'. So we skip that "fake" source. + let mut subgraphs = self + .federated_query_graph + .sources() + .filter(|&(name, _schema)| name != "_"); + if let (Some((subgraph_name, _subgraph_schema)), None) = + (subgraphs.next(), subgraphs.next()) + { + let node = FetchNode { + subgraph_name: subgraph_name.clone(), + operation_document: document.clone(), + operation_name: operation_name.as_deref().cloned(), + operation_kind: operation.operation_type, + id: None, + variable_usages: operation + .variables + .iter() + .map(|var| var.name.clone()) + .collect(), + requires: Default::default(), + input_rewrites: Default::default(), + output_rewrites: Default::default(), + }; + + return Ok(QueryPlan::new(node, statistics)); + } + } + + let reuse_query_fragments = self.config.reuse_query_fragments; + let mut named_fragments = NamedFragments::new(&document.fragments, &self.api_schema); + if reuse_query_fragments { + // For all subgraph fetches we query `__typename` on every abstract types (see + // `FetchDependencyGraphNode::to_plan_node`) so if we want to have a chance to reuse + // fragments, we should make sure those fragments also query `__typename` for every + // abstract type. + named_fragments = + named_fragments.add_typename_field_for_abstract_types_in_named_fragments()?; + } + + let normalized_operation = normalize_operation( + operation, + named_fragments, + &self.api_schema, + &self.interface_types_with_interface_objects, + )?; + + let (normalized_operation, assigned_defer_labels, defer_conditions, has_defers) = + if self.config.incremental_delivery.enable_defer { + let NormalizedDefer { + operation, + assigned_defer_labels, + defer_conditions, + has_defers, + } = normalized_operation.with_normalized_defer(); + if has_defers && is_subscription { + return Err(SingleFederationError::DeferredSubscriptionUnsupported.into()); + } + ( + operation, + Some(assigned_defer_labels), + Some(defer_conditions), + has_defers, + ) + } else { + // If defer is not enabled, we remove all @defer from the query. This feels cleaner do this once here than + // having to guard all the code dealing with defer later, and is probably less error prone too (less likely + // to end up passing through a @defer to a subgraph by mistake). + (normalized_operation.without_defer(), None, None, false) + }; + + if normalized_operation.selection_set.selections.is_empty() { + return Ok(QueryPlan::default()); + } + + let Some(root) = self + .federated_query_graph + .root_kinds_to_nodes()? + .get(&normalized_operation.root_kind) + else { + panic!( + "Shouldn't have a {0} operation if the subgraphs don't have a {0} root", + normalized_operation.root_kind + ); + }; + + let processor = FetchDependencyGraphToQueryPlanProcessor::new( + operation.variables.clone(), + Some(RebasedFragments::new(&normalized_operation.named_fragments)), + operation_name.clone(), + assigned_defer_labels, + ); + let mut parameters = QueryPlanningParameters { + supergraph_schema: self.supergraph_schema.clone(), + federated_query_graph: self.federated_query_graph.clone(), + operation: Arc::new(normalized_operation), + processor, + head: *root, + // PORT_NOTE(@goto-bus-stop): In JS, `root` is a `RootVertex`, which is dynamically + // checked at various points in query planning. This is our Rust equivalent of that. + head_must_be_root: true, + statistics, + abstract_types_with_inconsistent_runtime_types: self + .abstract_types_with_inconsistent_runtime_types + .clone() + .into(), + config: self.config.clone(), + // PORT_NOTE: JS provides `override_conditions` here: see port note in `QueryPlanner::new`. + }; + + let root_node = match defer_conditions { + Some(defer_conditions) if !defer_conditions.is_empty() => { + compute_plan_for_defer_conditionals(&mut parameters, defer_conditions)? + } + _ => compute_plan_internal(&mut parameters, has_defers)?, + }; + + let root_node = match root_node { + // If this is a subscription, we want to make sure that we return a SubscriptionNode rather than a PlanNode + // We potentially will need to separate "primary" from "rest" + // Note that if it is a subscription, we are guaranteed that nothing is deferred. + Some(PlanNode::Fetch(root_node)) if is_subscription => Some( + TopLevelPlanNode::Subscription(crate::query_plan::SubscriptionNode { + primary: root_node, + rest: None, + }), + ), + Some(PlanNode::Sequence(root_node)) if is_subscription => { + let Some((primary, rest)) = root_node.nodes.split_first() else { + unreachable!("Sequence must have at least one node"); + }; + let PlanNode::Fetch(primary) = primary.clone() else { + unreachable!("Primary node of a subscription is not a Fetch"); + }; + let rest = PlanNode::Sequence(SequenceNode { + nodes: rest.to_vec(), + }); + Some(TopLevelPlanNode::Subscription( + crate::query_plan::SubscriptionNode { + primary, + rest: Some(Box::new(rest)), + }, + )) + } + Some(node) if is_subscription => { + unreachable!( + "Unexpected top level PlanNode: '{node:?}' when processing subscription" + ) + } + Some(PlanNode::Fetch(inner)) => Some(TopLevelPlanNode::Fetch(inner)), + Some(PlanNode::Sequence(inner)) => Some(TopLevelPlanNode::Sequence(inner)), + Some(PlanNode::Parallel(inner)) => Some(TopLevelPlanNode::Parallel(inner)), + Some(PlanNode::Flatten(inner)) => Some(TopLevelPlanNode::Flatten(inner)), + Some(PlanNode::Defer(inner)) => Some(TopLevelPlanNode::Defer(inner)), + Some(PlanNode::Condition(inner)) => Some(TopLevelPlanNode::Condition(inner)), + None => None, + }; + + Ok(QueryPlan { + node: root_node, + statistics: parameters.statistics, + }) + } + + /// Get Query Planner's API Schema. + pub fn api_schema(&self) -> &ValidFederationSchema { + &self.api_schema + } +} + +fn compute_root_serial_dependency_graph( + _parameters: &QueryPlanningParameters, + _has_defers: bool, +) -> Result, FederationError> { + todo!("FED-127") +} + +fn compute_root_parallel_dependency_graph( + parameters: &QueryPlanningParameters, + has_defers: bool, +) -> Result { + let selection_set = parameters.operation.selection_set.clone(); + let best_plan = compute_root_parallel_best_plan(parameters, selection_set, has_defers)?; + Ok(best_plan.fetch_dependency_graph) +} + +fn compute_root_parallel_best_plan( + parameters: &QueryPlanningParameters, + selection: SelectionSet, + has_defers: bool, +) -> Result { + let planning_traversal = QueryPlanningTraversal::new( + parameters, + selection, + has_defers, + parameters.operation.root_kind, + FetchDependencyGraphToCostProcessor, + )?; + + // Getting no plan means the query is essentially unsatisfiable (it's a valid query, but we can prove it will never return a result), + // so we just return an empty plan. + Ok(planning_traversal + .find_best_plan()? + .unwrap_or_else(|| BestQueryPlanInfo::empty(parameters))) +} + +fn compute_plan_internal( + parameters: &mut QueryPlanningParameters, + has_defers: bool, +) -> Result, FederationError> { + let root_kind = parameters.operation.root_kind; + + let (main, deferred, primary_selection) = if root_kind == SchemaRootDefinitionKind::Mutation { + let dependency_graphs = compute_root_serial_dependency_graph(parameters, has_defers)?; + let mut main = None; + let mut deferred = vec![]; + let mut primary_selection = None::; + for mut dependency_graph in dependency_graphs { + let (local_main, local_deferred) = + dependency_graph.process(&mut parameters.processor, root_kind)?; + main = match main { + Some(unlocal_main) => parameters + .processor + .reduce_sequence([Some(unlocal_main), local_main]), + None => local_main, + }; + deferred.extend(local_deferred); + let new_selection = dependency_graph.defer_tracking.primary_selection; + match primary_selection.as_mut() { + Some(selection) => selection.merge_into(new_selection.iter())?, + None => primary_selection = new_selection, + } + } + (main, deferred, primary_selection) + } else { + let mut dependency_graph = compute_root_parallel_dependency_graph(parameters, has_defers)?; + + let (main, deferred) = dependency_graph.process(&mut parameters.processor, root_kind)?; + // XXX(@goto-bus-stop) Maybe `.defer_tracking` should be on the return value of `process()`..? + let primary_selection = dependency_graph.defer_tracking.primary_selection; + + (main, deferred, primary_selection) + }; + + if deferred.is_empty() { + Ok(main) + } else { + let Some(primary_selection) = primary_selection else { + unreachable!("Should have had a primary selection created"); + }; + parameters + .processor + .reduce_defer(main, &primary_selection, deferred) + } +} + +fn compute_plan_for_defer_conditionals( + _parameters: &mut QueryPlanningParameters, + _defer_conditions: IndexMap>, +) -> Result, FederationError> { + todo!("FED-95") +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::subgraph::Subgraph; + + const TEST_SUPERGRAPH: &str = r#" +schema + @link(url: "https://specs.apollo.dev/link/v1.0") + @link(url: "https://specs.apollo.dev/join/v0.2", for: EXECUTION) +{ + query: Query +} + +directive @join__field(graph: join__Graph!, requires: join__FieldSet, provides: join__FieldSet, type: String, external: Boolean, override: String, usedOverridden: Boolean) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION + +directive @join__graph(name: String!, url: String!) on ENUM_VALUE + +directive @join__implements(graph: join__Graph!, interface: String!) repeatable on OBJECT | INTERFACE + +directive @join__type(graph: join__Graph!, key: join__FieldSet, extension: Boolean! = false, resolvable: Boolean! = true) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR + +directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA + +type Book implements Product + @join__implements(graph: PRODUCTS, interface: "Product") + @join__implements(graph: REVIEWS, interface: "Product") + @join__type(graph: PRODUCTS, key: "id") + @join__type(graph: REVIEWS, key: "id") +{ + id: ID! + price: Price @join__field(graph: PRODUCTS) + title: String @join__field(graph: PRODUCTS) + vendor: User @join__field(graph: PRODUCTS) + pages: Int @join__field(graph: PRODUCTS) + avg_rating: Int @join__field(graph: PRODUCTS, requires: "reviews { rating }") + reviews: [Review] @join__field(graph: PRODUCTS, external: true) @join__field(graph: REVIEWS) +} + +enum Currency + @join__type(graph: PRODUCTS) +{ + USD + EUR +} + +scalar join__FieldSet + +enum join__Graph { + ACCOUNTS @join__graph(name: "accounts", url: "") + PRODUCTS @join__graph(name: "products", url: "") + REVIEWS @join__graph(name: "reviews", url: "") +} + +scalar link__Import + +enum link__Purpose { + """ + `SECURITY` features provide metadata necessary to securely resolve fields. + """ + SECURITY + + """ + `EXECUTION` features provide metadata necessary for operation execution. + """ + EXECUTION +} + +type Movie implements Product + @join__implements(graph: PRODUCTS, interface: "Product") + @join__implements(graph: REVIEWS, interface: "Product") + @join__type(graph: PRODUCTS, key: "id") + @join__type(graph: REVIEWS, key: "id") +{ + id: ID! + price: Price @join__field(graph: PRODUCTS) + title: String @join__field(graph: PRODUCTS) + vendor: User @join__field(graph: PRODUCTS) + length_minutes: Int @join__field(graph: PRODUCTS) + avg_rating: Int @join__field(graph: PRODUCTS, requires: "reviews { rating }") + reviews: [Review] @join__field(graph: PRODUCTS, external: true) @join__field(graph: REVIEWS) +} + +type Price + @join__type(graph: PRODUCTS) +{ + value: Int + currency: Currency +} + +interface Product + @join__type(graph: PRODUCTS) + @join__type(graph: REVIEWS) +{ + id: ID! + price: Price @join__field(graph: PRODUCTS) + vendor: User @join__field(graph: PRODUCTS) + avg_rating: Int @join__field(graph: PRODUCTS) + reviews: [Review] @join__field(graph: REVIEWS) +} + +type Query + @join__type(graph: ACCOUNTS) + @join__type(graph: PRODUCTS) + @join__type(graph: REVIEWS) +{ + userById(id: ID!): User @join__field(graph: ACCOUNTS) + me: User! @join__field(graph: ACCOUNTS) @join__field(graph: REVIEWS) + productById(id: ID!): Product @join__field(graph: PRODUCTS) + search(filter: SearchFilter): [Product] @join__field(graph: PRODUCTS) + bestRatedProducts(limit: Int): [Product] @join__field(graph: REVIEWS) +} + +type Review + @join__type(graph: PRODUCTS) + @join__type(graph: REVIEWS) +{ + rating: Int @join__field(graph: PRODUCTS, external: true) @join__field(graph: REVIEWS) + product: Product @join__field(graph: REVIEWS) + author: User @join__field(graph: REVIEWS) + text: String @join__field(graph: REVIEWS) +} + +input SearchFilter + @join__type(graph: PRODUCTS) +{ + pattern: String! + vendorName: String +} + +type User + @join__type(graph: ACCOUNTS, key: "id") + @join__type(graph: PRODUCTS, key: "id", resolvable: false) + @join__type(graph: REVIEWS, key: "id") +{ + id: ID! + name: String @join__field(graph: ACCOUNTS) + email: String @join__field(graph: ACCOUNTS) + password: String @join__field(graph: ACCOUNTS) + nickname: String @join__field(graph: ACCOUNTS, override: "reviews") + reviews: [Review] @join__field(graph: REVIEWS) +} + "#; + + #[test] + fn plan_simple_query_for_single_subgraph() { + let supergraph = Supergraph::new(TEST_SUPERGRAPH).unwrap(); + let planner = QueryPlanner::new(&supergraph, Default::default()).unwrap(); + + let document = ExecutableDocument::parse_and_validate( + planner.api_schema().schema(), + r#" + { + userById(id: 1) { + name + email + } + } + "#, + "operation.graphql", + ) + .unwrap(); + let plan = planner.build_query_plan(&document, None).unwrap(); + insta::assert_snapshot!(plan, @r###" + QueryPlan { + Fetch(service: "accounts") { + { + userById(id: 1) { + name + email + } + } + }, + } + "###); + } + + #[test] + fn plan_simple_query_for_multiple_subgraphs() { + let supergraph = Supergraph::new(TEST_SUPERGRAPH).unwrap(); + let planner = QueryPlanner::new(&supergraph, Default::default()).unwrap(); + + let document = ExecutableDocument::parse_and_validate( + planner.api_schema().schema(), + r#" + { + bestRatedProducts { + vendor { name } + } + } + "#, + "operation.graphql", + ) + .unwrap(); + let plan = planner.build_query_plan(&document, None).unwrap(); + // TODO: This is the current output, but it's wrong: it's not fetching `vendor.name` at all. + insta::assert_snapshot!(plan, @r###" + QueryPlan { + Sequence { + Fetch(service: "reviews") { + { + bestRatedProducts { + ... on Book { + __typename + id + } + ... on Movie { + __typename + id + } + } + } + }, + Parallel { + Sequence { + Flatten(path: "bestRatedProducts.*") { + Fetch(service: "products") { + { + ... on Movie { + __typename + id + } + } => + { + ... on Movie { + vendor { + __typename + id + } + } + } + }, + }, + Flatten(path: "bestRatedProducts.*.vendor") { + Fetch(service: "accounts") { + { + ... on User { + __typename + id + } + } => + { + ... on User { + name + } + } + }, + }, + }, + Sequence { + Flatten(path: "bestRatedProducts.*") { + Fetch(service: "products") { + { + ... on Book { + __typename + id + } + } => + { + ... on Book { + vendor { + __typename + id + } + } + } + }, + }, + Flatten(path: "bestRatedProducts.*.vendor") { + Fetch(service: "accounts") { + { + ... on User { + __typename + id + } + } => + { + ... on User { + name + } + } + }, + }, + }, + }, + }, + } + "###); + } + + // TODO: This fails with "Subgraph unexpectedly does not use federation spec" + // which seems...unusual + #[test] + #[ignore] + fn plan_simple_root_field_query_for_multiple_subgraphs() { + let supergraph = Supergraph::new(TEST_SUPERGRAPH).unwrap(); + let planner = QueryPlanner::new(&supergraph, Default::default()).unwrap(); + + let document = ExecutableDocument::parse_and_validate( + planner.api_schema().schema(), + r#" + { + userById(id: 1) { + name + email + } + bestRatedProducts { + id + avg_rating + } + } + "#, + "operation.graphql", + ) + .unwrap(); + let plan = planner.build_query_plan(&document, None).unwrap(); + insta::assert_snapshot!(plan, @r###" + QueryPlan { + Parallel { + Fetch(service: "accounts") { + { + userById(id: 1) { + name + email + } + } + } + Fetch(service: "products") { + { + bestRatedProducts { + id + avg_rating + } + } + } + } + } + "###); + } + + #[test] + fn bypass_planner_for_single_subgraph() { + let a = Subgraph::parse_and_expand( + "A", + "https://A", + r#" + type Query { + a: A + } + type A { + b: B + } + type B { + x: Int + y: String + } + "#, + ) + .unwrap(); + let subgraphs = vec![&a]; + let supergraph = Supergraph::compose(subgraphs).unwrap(); + let api_schema = supergraph.to_api_schema(Default::default()).unwrap(); + + let document = ExecutableDocument::parse_and_validate( + api_schema.schema(), + r#" + { + a { + b { + x + y + } + } + } + "#, + "", + ) + .unwrap(); + + let mut config = QueryPlannerConfig::default(); + config.debug.bypass_planner_for_single_subgraph = true; + let planner = QueryPlanner::new(&supergraph, config).unwrap(); + let plan = planner.build_query_plan(&document, None).unwrap(); + insta::assert_snapshot!(plan, @r###" + QueryPlan { + Fetch(service: "A") { + { + a { + b { + x + y + } + } + } + }, + } + "###); + } +} diff --git a/apollo-federation/src/query_plan/query_planning_traversal.rs b/apollo-federation/src/query_plan/query_planning_traversal.rs new file mode 100644 index 0000000000..5fe3f3b125 --- /dev/null +++ b/apollo-federation/src/query_plan/query_planning_traversal.rs @@ -0,0 +1,1125 @@ +use std::sync::Arc; + +use indexmap::IndexSet; +use petgraph::graph::EdgeIndex; +use petgraph::graph::NodeIndex; + +use crate::error::FederationError; +use crate::query_graph::condition_resolver::ConditionResolution; +use crate::query_graph::condition_resolver::ConditionResolutionCacheResult; +use crate::query_graph::condition_resolver::ConditionResolver; +use crate::query_graph::condition_resolver::ConditionResolverCache; +use crate::query_graph::graph_path::create_initial_options; +use crate::query_graph::graph_path::ClosedBranch; +use crate::query_graph::graph_path::ClosedPath; +use crate::query_graph::graph_path::ExcludedConditions; +use crate::query_graph::graph_path::ExcludedDestinations; +use crate::query_graph::graph_path::OpGraphPath; +use crate::query_graph::graph_path::OpGraphPathContext; +use crate::query_graph::graph_path::OpPathElement; +use crate::query_graph::graph_path::OpenBranch; +use crate::query_graph::graph_path::SimultaneousPaths; +use crate::query_graph::graph_path::SimultaneousPathsWithLazyIndirectPaths; +use crate::query_graph::path_tree::OpPathTree; +use crate::query_graph::QueryGraph; +use crate::query_graph::QueryGraphNodeType; +use crate::query_plan::fetch_dependency_graph::compute_nodes_for_tree; +use crate::query_plan::fetch_dependency_graph::FetchDependencyGraph; +use crate::query_plan::fetch_dependency_graph_processor::FetchDependencyGraphProcessor; +use crate::query_plan::fetch_dependency_graph_processor::FetchDependencyGraphToCostProcessor; +use crate::query_plan::fetch_dependency_graph_processor::FetchDependencyGraphToQueryPlanProcessor; +use crate::query_plan::generate::generate_all_plans_and_find_best; +use crate::query_plan::generate::PlanBuilder; +use crate::query_plan::operation::Operation; +use crate::query_plan::operation::Selection; +use crate::query_plan::operation::SelectionSet; +use crate::query_plan::query_planner::QueryPlannerConfig; +use crate::query_plan::query_planner::QueryPlanningStatistics; +use crate::query_plan::QueryPlanCost; +use crate::schema::position::AbstractTypeDefinitionPosition; +use crate::schema::position::CompositeTypeDefinitionPosition; +use crate::schema::position::ObjectTypeDefinitionPosition; +use crate::schema::position::OutputTypeDefinitionPosition; +use crate::schema::position::SchemaRootDefinitionKind; +use crate::schema::ValidFederationSchema; + +// PORT_NOTE: Named `PlanningParameters` in the JS codebase, but there was no particular reason to +// leave out to the `Query` prefix, so it's been added for consistency. Similar to `GraphPath`, we +// don't have a distinguished type for when the head is a root vertex, so we instead check this at +// runtime (introducing the new field `head_must_be_root`). +// NOTE: `head_must_be_root` can be deduced from the `head` node's type, so we might be able to +// remove it. +pub(crate) struct QueryPlanningParameters { + /// The supergraph schema that generated the federated query graph. + pub(crate) supergraph_schema: ValidFederationSchema, + /// The federated query graph used for query planning. + pub(crate) federated_query_graph: Arc, + /// The operation to be query planned. + pub(crate) operation: Arc, + /// A processor for converting fetch dependency graphs to query plans. + pub(crate) processor: FetchDependencyGraphToQueryPlanProcessor, + /// The query graph node at which query planning begins. + pub(crate) head: NodeIndex, + /// Whether the head must be a root node for query planning. + pub(crate) head_must_be_root: bool, + /// A set of the names of interface or union types that have inconsistent "runtime types" across + /// subgraphs. + // PORT_NOTE: Named `inconsistentAbstractTypesRuntimes` in the JS codebase, which was slightly + // confusing. + pub(crate) abstract_types_with_inconsistent_runtime_types: + Arc>, + /// The configuration for the query planner. + pub(crate) config: QueryPlannerConfig, + pub(crate) statistics: QueryPlanningStatistics, +} + +pub(crate) struct QueryPlanningTraversal<'a> { + /// The parameters given to query planning. + parameters: &'a QueryPlanningParameters, + /// The root kind of the operation. + root_kind: SchemaRootDefinitionKind, + /// True if query planner `@defer` support is enabled and the operation contains some `@defer` + /// application. + has_defers: bool, + /// The initial fetch ID generation (used when handling `@defer`). + starting_id_generation: u64, + /// A processor for converting fetch dependency graphs to cost. + cost_processor: FetchDependencyGraphToCostProcessor, + /// True if this query planning is at top-level (note that query planning can recursively start + /// further query planning). + is_top_level: bool, + /// The stack of open branches left to plan, along with state indicating the next selection to + /// plan for them. + // PORT_NOTE: The `stack` in the JS codebase only contained one selection per stack entry, but + // to avoid having to clone the `OpenBranch` structures (which loses the benefits of indirect + // path caching), we create a multi-level-stack here, where the top-level stack is over open + // branches and the sub-stack is over selections. + open_branches: Vec, + /// The closed branches that have been planned. + closed_branches: Vec, + /// The best plan found as a result of query planning. + // TODO(@goto-bus-stop): FED-164: can we remove this? `find_best_plan` consumes `self` and returns the + // best plan, so it should not be necessary to store it. + best_plan: Option, + /// The cache for condition resolution. + // PORT_NOTE: This is different from JS version. See `ConditionResolver` trait implementation below. + resolver_cache: ConditionResolverCache, +} + +#[derive(Debug)] +struct OpenBranchAndSelections { + /// The options for this open branch. + open_branch: OpenBranch, + /// A stack of the remaining selections to plan from the node this open branch ends on. + selections: Vec, +} + +struct PlanInfo { + fetch_dependency_graph: FetchDependencyGraph, + path_tree: Arc, +} + +impl std::fmt::Debug for PlanInfo { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(&self.path_tree, f) + } +} + +pub(crate) struct BestQueryPlanInfo { + /// The fetch dependency graph for this query plan. + pub fetch_dependency_graph: FetchDependencyGraph, + /// The path tree for the closed branch options chosen for this query plan. + pub path_tree: Arc, + /// The cost of this query plan. + pub cost: QueryPlanCost, +} + +impl BestQueryPlanInfo { + // PORT_NOTE: The equivalent of `createEmptyPlan` in the JS codebase. + pub fn empty(parameters: &QueryPlanningParameters) -> Self { + Self { + fetch_dependency_graph: FetchDependencyGraph::new( + parameters.supergraph_schema.clone(), + parameters.federated_query_graph.clone(), + None, + 0, + ), + path_tree: OpPathTree::new(parameters.federated_query_graph.clone(), parameters.head) + .into(), + cost: Default::default(), + } + } +} + +impl<'a> QueryPlanningTraversal<'a> { + pub fn new( + // TODO(@goto-bus-stop): This probably needs a mutable reference for some of the + // yet-unimplemented methods, and storing a mutable ref in `Self` here smells bad. + // The ownership of `QueryPlanningParameters` is awkward and should probably be + // refactored. + parameters: &'a QueryPlanningParameters, + selection_set: SelectionSet, + has_defers: bool, + root_kind: SchemaRootDefinitionKind, + cost_processor: FetchDependencyGraphToCostProcessor, + ) -> Result { + Self::new_inner( + parameters, + selection_set, + 0, + has_defers, + root_kind, + cost_processor, + Default::default(), + Default::default(), + Default::default(), + ) + } + + // Many arguments is okay for a private constructor function. + #[allow(clippy::too_many_arguments)] + fn new_inner( + parameters: &'a QueryPlanningParameters, + selection_set: SelectionSet, + starting_id_generation: u64, + has_defers: bool, + root_kind: SchemaRootDefinitionKind, + cost_processor: FetchDependencyGraphToCostProcessor, + initial_context: OpGraphPathContext, + excluded_destinations: ExcludedDestinations, + excluded_conditions: ExcludedConditions, + ) -> Result { + let is_top_level = parameters.head_must_be_root; + + fn map_options_to_selections( + selection_set: SelectionSet, + options: Vec, + ) -> Vec { + let open_branch = OpenBranch(options); + let selections = selection_set.selections.values().cloned().rev().collect(); + vec![OpenBranchAndSelections { + open_branch, + selections, + }] + } + + let initial_path = OpGraphPath::new( + Arc::clone(¶meters.federated_query_graph), + parameters.head, + ) + .unwrap(); + // In JS this is done *inside* create_initial_options, which would require awareness of the + // query graph. + let tail = parameters + .federated_query_graph + .node_weight(initial_path.tail)?; + + // Two-step initialization: initializing open_branches requires a condition resolver, + // which `QueryPlanningTraversal` is. + let mut traversal = Self { + parameters, + root_kind, + has_defers, + starting_id_generation, + cost_processor, + is_top_level, + open_branches: Default::default(), + closed_branches: Default::default(), + best_plan: None, + resolver_cache: ConditionResolverCache::new(), + }; + + let initial_options = create_initial_options( + initial_path, + &tail.type_, + initial_context, + &mut traversal, + excluded_destinations, + excluded_conditions, + )?; + + traversal.open_branches = map_options_to_selections(selection_set, initial_options); + + Ok(traversal) + } + + // PORT_NOTE: In JS, the traversal is still usable after finding the best plan. Here we consume + // the struct so we do not need to return a reference, which is very unergonomic. + pub fn find_best_plan(mut self) -> Result, FederationError> { + self.find_best_plan_inner()?; + Ok(self.best_plan) + } + + fn find_best_plan_inner(&mut self) -> Result, FederationError> { + while let Some(mut current_branch) = self.open_branches.pop() { + let Some(current_selection) = current_branch.selections.pop() else { + return Err(FederationError::internal( + "Sub-stack unexpectedly empty during query plan traversal", + )); + }; + let (terminate_planning, new_branch) = + self.handle_open_branch(¤t_selection, &mut current_branch.open_branch.0)?; + if terminate_planning { + // We clear both open branches and closed ones as a means to terminate the plan + // computation with no plan. + self.open_branches = vec![]; + self.closed_branches = vec![]; + break; + } + if !current_branch.selections.is_empty() { + self.open_branches.push(current_branch); + } + if let Some(new_branch) = new_branch { + self.open_branches.push(new_branch); + } + } + self.compute_best_plan_from_closed_branches()?; + return Ok(self.best_plan.as_ref()); + } + + /// Returns whether to terminate planning immediately, and any new open branches to push onto + /// the stack. + fn handle_open_branch( + &mut self, + selection: &Selection, + options: &mut Vec, + ) -> Result<(bool, Option), FederationError> { + let operation_element = selection.element()?; + let mut new_options = vec![]; + let mut no_followups: bool = false; + for option in options.iter_mut() { + let followups_for_option = option.advance_with_operation_element( + self.parameters.supergraph_schema.clone(), + &operation_element, + /*resolver*/ self, + )?; + let Some(followups_for_option) = followups_for_option else { + // There is no valid way to advance the current operation element from this option + // so this option is a dead branch that cannot produce a valid query plan. So we + // simply ignore it and rely on other options. + continue; + }; + if followups_for_option.is_empty() { + // See the comment above where we check `no_followups` for more information. + no_followups = true; + break; + } + new_options.extend(followups_for_option); + if let Some(options_limit) = self.parameters.config.debug.paths_limit { + if new_options.len() > options_limit as usize { + // TODO: Create a new error code for this error kind. + return Err(FederationError::internal(format!( + "Too many options generated for {}, reached the limit of {}.", + selection, options_limit, + ))); + } + } + } + + if no_followups { + // This operation element is valid from this option, but is guarantee to yield no result + // (e.g. it's a type condition with no intersection with a prior type condition). Given + // that all options return the same results (assuming the user does properly resolve all + // versions of a given field the same way from all subgraphs), we know that the + // operation element should return no result from all options (even if we can't provide + // it technically). + // + // More concretely, this usually means the current operation element is a type condition + // that has no intersection with the possible current runtime types at this point, and + // this means whatever fields the type condition sub-selection selects, they will never + // be part of the results. That said, we cannot completely ignore the + // type-condition/fragment or we'd end up with the wrong results. Consider this example + // where a sub-part of the query is: + // { + // foo { + // ... on Bar { + // field + // } + // } + // } + // and suppose that `... on Bar` can never match a concrete runtime type at this point. + // Because that's the only sub-selection of `foo`, if we completely ignore it, we'll end + // up not querying this at all. Which means that, during execution, we'd either return + // (for that sub-part of the query) `{ foo: null }` if `foo` happens to be nullable, or + // just `null` for the whole sub-part otherwise. But what we *should* return (assuming + // foo doesn't actually return `null`) is `{ foo: {} }`. Meaning, we have queried `foo` + // and it returned something, but it's simply not a `Bar` and so nothing was included. + // + // Long story short, to avoid that situation, we replace the whole `... on Bar` section + // that can never match the runtime type by simply getting the `__typename` of `foo`. + // This ensure we do query `foo` but don't end up including conditions that may not even + // make sense to the subgraph we're querying. Do note that we'll only need that + // `__typename` if there is no other selections inside `foo`, and so we might include it + // unnecessarily in practice: it's a very minor inefficiency though. + if matches!(operation_element, OpPathElement::InlineFragment(_)) { + let mut closed_paths = vec![]; + for option in options { + let mut new_simultaneous_paths = vec![]; + for simultaneous_path in &option.paths.0 { + new_simultaneous_paths.push(Arc::new( + simultaneous_path.terminate_with_non_requested_typename_field()?, + )); + } + closed_paths.push(Arc::new(ClosedPath { + paths: SimultaneousPaths(new_simultaneous_paths), + selection_set: None, + })); + } + self.record_closed_branch(ClosedBranch(closed_paths))?; + } + return Ok((false, None)); + } + + if new_options.is_empty() { + // If we have no options, it means there is no way to build a plan for that branch, and + // that means the whole query planning process will generate no plan. This should never + // happen for a top-level query planning (unless the supergraph has *not* been + // validated), but can happen when computing sub-plans for a key condition. + return if self.is_top_level { + Err(FederationError::internal(format!( + "Was not able to find any options for {}: This shouldn't have happened.", + selection, + ))) + } else { + // Indicate to the caller that query planning should terminate with no plan. + Ok((true, None)) + }; + } + + if let Some(selection_set) = selection.selection_set()? { + let mut all_tail_nodes = IndexSet::new(); + for option in &new_options { + for path in &option.paths.0 { + all_tail_nodes.insert(path.tail); + } + } + if self.selection_set_is_fully_local_from_all_nodes(selection_set, &all_tail_nodes)? + && !selection.has_defer() + { + // We known the rest of the selection is local to whichever subgraph the current + // options are in, and so we're going to keep that selection around and add it + // "as-is" to the `FetchDependencyGraphNode` when needed, saving a bunch of work + // (creating `GraphPath`, merging `PathTree`, ...). However, as we're skipping the + // "normal path" for that sub-selection, there are a few things that are handled in + // said "normal path" that we need to still handle. + // + // More precisely: + // - We have this "attachment" trick that removes requested `__typename` + // temporarily, so we should add it back. + // - We still need to add the selection of `__typename` for abstract types. It is + // not really necessary for the execution per-se, but if we don't do it, then we + // will not be able to reuse named fragments as often as we should (we add + // `__typename` for abstract types on the "normal path" and so we add them too to + // named fragments; as such, we need them here too). + let new_selection_set = Arc::new( + selection_set + .add_back_typename_in_attachments()? + .add_typename_field_for_abstract_types(None, &None)?, + ); + self.record_closed_branch(ClosedBranch( + new_options + .into_iter() + .map(|option| { + Arc::new(ClosedPath { + paths: option.paths, + selection_set: Some(new_selection_set.clone()), + }) + }) + .collect(), + ))?; + } else { + return Ok(( + false, + Some(OpenBranchAndSelections { + open_branch: OpenBranch(new_options), + selections: selection_set.selections.values().cloned().rev().collect(), + }), + )); + } + } else { + self.record_closed_branch(ClosedBranch( + new_options + .into_iter() + .map(|option| { + Arc::new(ClosedPath { + paths: option.paths, + selection_set: None, + }) + }) + .collect(), + ))?; + } + + Ok((false, None)) + } + + fn record_closed_branch(&mut self, closed_branch: ClosedBranch) -> Result<(), FederationError> { + let maybe_trimmed = closed_branch.maybe_eliminate_strictly_more_costly_paths()?; + self.closed_branches.push(maybe_trimmed); + Ok(()) + } + + fn selection_set_is_fully_local_from_all_nodes( + &self, + selection: &SelectionSet, + nodes: &IndexSet, + ) -> Result { + // To guarantee that the selection is fully local from the provided vertex/type, we must have: + // - no edge crossing subgraphs from that vertex. + // - the type must be compositeType (mostly just ensuring the selection make sense). + // - everything in the selection must be avaiable in the type (which `rebaseOn` essentially validates). + // - the selection must not "type-cast" into any abstract type that has inconsistent runtimes acrosse subgraphs. The reason for the + // later condition is that `selection` is originally a supergraph selection, but that we're looking to apply "as-is" to a subgraph. + // But suppose it has a `... on I` where `I` is an interface. Then it's possible that `I` includes "more" types in the supergraph + // than in the subgraph, and so we might have to type-explode it. If so, we cannot use the selection "as-is". + // PORT_NOTE: The JS code performs the last check lazily. Instead of that, this check is + // skipped if `nodes` is empty. + if !nodes.is_empty() + && selection.selections.values().any(|val| match val { + Selection::InlineFragment(fragment) => { + match &fragment.inline_fragment.data().type_condition_position { + Some(type_condition) => self + .parameters + .abstract_types_with_inconsistent_runtime_types + .iter() + .any(|ty| ty.type_name() == type_condition.type_name()), + None => false, + } + } + _ => false, + }) + { + return Ok(false); + } + for node in nodes { + let n = self.parameters.federated_query_graph.node_weight(*node)?; + let parent_ty = match &n.type_ { + QueryGraphNodeType::SchemaType(ty) => { + match CompositeTypeDefinitionPosition::try_from(ty.clone()) { + Ok(ty) => ty, + _ => return Ok(false), + } + } + QueryGraphNodeType::FederatedRootType(_) => return Ok(false), + }; + if n.has_reachable_cross_subgraph_edges || !selection.can_rebase_on(&parent_ty) { + return Ok(false); + } + } + Ok(true) + } + + fn cost( + &mut self, + dependency_graph: &mut FetchDependencyGraph, + ) -> Result { + let (main, deferred) = dependency_graph.process(self.cost_processor, self.root_kind)?; + if deferred.is_empty() { + Ok(main) + } else { + let Some(primary_selection) = + dependency_graph.defer_tracking.primary_selection.as_ref() + else { + // PORT_NOTE: The JS version unwraps here. + return Err(FederationError::internal( + "Primary selection not set in fetch dependency graph", + )); + }; + self.cost_processor + .reduce_defer(main, primary_selection, deferred) + } + } + + fn compute_best_plan_from_closed_branches(&mut self) -> Result<(), FederationError> { + if self.closed_branches.is_empty() { + return Ok(()); + } + self.prune_closed_branches(); + self.sort_options_in_closed_branches()?; + self.reduce_options_if_needed(); + + // debug log + // self.closed_branches + // .iter() + // .enumerate() + // .for_each(|(i, branch)| { + // println!("{i}:"); + // branch.0.iter().for_each(|path| { + // println!(" - {path}"); + // }); + // }); + + // Note that usually we'll have a majority of branches with just one option. We can group them in + // a PathTree first with no fuss. When then need to do a cartesian product between this created + // tree an other branches however to build the possible plans and chose. + + // find the index of the branch with only one path in self.closed_branches. + let sole_path_branch_index = self + .closed_branches + .iter() + .position(|branch| branch.0.len() == 1) + .unwrap_or(self.closed_branches.len()); + // first_group: the first half of branches that have multiple choices. + // second_group: the second half starting with a branch that has only one choice. + let (first_group, second_group) = self.closed_branches.split_at(sole_path_branch_index); + + let initial_tree; + let mut initial_dependency_graph = self.new_dependency_graph(); + let federated_query_graph = &self.parameters.federated_query_graph; + let root = &self.parameters.head; + if second_group.is_empty() { + // Unfortunately, all branches have more than one choices. + initial_tree = OpPathTree::new(federated_query_graph.clone(), *root); + } else { + // Build a tree with the second group's paths. + let single_choice_branches: Vec<_> = second_group + .iter() + .flat_map(|b| &b.0) + .flat_map(|cp| cp.flatten()) + .collect(); + initial_tree = OpPathTree::from_op_paths( + federated_query_graph.clone(), + *root, + &single_choice_branches, + )?; + self.updated_dependency_graph(&mut initial_dependency_graph, &initial_tree)?; + if first_group.is_empty() { + // Well, we have the only possible plan; it's also the best. + let cost = self.cost(&mut initial_dependency_graph)?; + self.best_plan = BestQueryPlanInfo { + fetch_dependency_graph: initial_dependency_graph, + path_tree: initial_tree.into(), + cost, + } + .into(); + return Ok(()); + } + } + + // Build trees from the first group + let other_trees: Vec>>> = first_group + .iter() + .map(|b| { + b.0.iter() + .map(|opt| { + OpPathTree::from_op_paths( + federated_query_graph.clone(), + *root, + &Vec::from_iter(opt.flatten()), + ) + .ok() + .map(Arc::new) + }) + .collect() + }) + .collect(); + + let (best, cost) = generate_all_plans_and_find_best( + PlanInfo { + fetch_dependency_graph: initial_dependency_graph, + path_tree: Arc::new(initial_tree), + }, + other_trees, + /*plan_builder*/ self, + )?; + self.best_plan = BestQueryPlanInfo { + fetch_dependency_graph: best.fetch_dependency_graph, + path_tree: best.path_tree, + cost, + } + .into(); + Ok(()) + } + + /// Remove closed branches that are known to be overridden by others. + /// + /// We've computed all branches and need to compare all the possible plans to pick the best. + /// Note however that "all the possible plans" is essentially a cartesian product of all + /// the closed branches options, and if a lot of branches have multiple options, this can + /// exponentially explode. + /// So first, we check if we can preemptively prune some branches based on + /// those branches having options that are known to be overriden by other ones. + fn prune_closed_branches(&mut self) { + for branch in &mut self.closed_branches { + if branch.0.len() <= 1 { + continue; + } + + let mut pruned = ClosedBranch(Vec::new()); + for (i, to_check) in branch.0.iter().enumerate() { + if !Self::option_is_overriden(i, &to_check.paths, branch) { + pruned.0.push(to_check.clone()); + } + } + + *branch = pruned + } + } + + fn option_is_overriden( + index: usize, + to_check: &SimultaneousPaths, + all_options: &ClosedBranch, + ) -> bool { + all_options + .0 + .iter() + .enumerate() + // Don’t compare `to_check` with itself + .filter(|&(i, _)| i != index) + .any(|(_i, option)| { + to_check + .0 + .iter() + .all(|p| option.paths.0.iter().any(|o| p.is_overridden_by(o))) + }) + } + + /// We now sort the options within each branch, + /// putting those with the least amount of subgraph jumps first. + /// The idea is that for each branch taken individually, + /// the option with the least jumps is going to be the most efficient, + /// and while it is not always the case that the best plan is built for those individual bests, + /// they are still statistically more likely to be part of the best plan. + /// So putting them first has 2 benefits for the rest of this method: + /// + /// 1. if we end up cutting some options of a branch below + /// (due to having too many possible plans), + /// we'll cut the last option first (we `pop()`), + /// so better cut what it the least likely to be good. + /// 2. when we finally generate the plan, + /// we use the cost of previously computed plans to cut computation early when possible + /// (see `generate_all_plans_and_find_best`), + /// so there is a premium in generating good plans early (it cuts more computation), + /// and putting those more-likely-to-be-good options first helps this. + fn sort_options_in_closed_branches(&mut self) -> Result<(), FederationError> { + for branch in &mut self.closed_branches { + let mut result = Ok(()); + branch.0.sort_by_key(|branch| { + branch + .paths + .0 + .iter() + .try_fold(0, |max_so_far, path| { + Ok(max_so_far.max(path.subgraph_jumps()?)) + }) + .unwrap_or_else(|err: FederationError| { + // There’s no way to abort `sort_by_key` from this callback. + // Store the error to be returned later and return an dummy values + result = Err(err); + 0 + }) + }); + result? + } + Ok(()) + } + + /// Look at how many plans we'd have to generate and if it's "too much" + /// reduce it to something manageable by arbitrarilly throwing out options. + /// This effectively means that when a query has too many options, + /// we give up on always finding the "best" query plan in favor of an "ok" query plan. + /// + /// TODO: currently, when we need to reduce options, we do so somewhat arbitrarilly. + /// More precisely, we reduce the branches with the most options first + /// and then drop the last option of the branch, + /// repeating until we have a reasonable number of plans to consider. + /// The sorting we do about help making this slightly more likely to be a good choice, + /// but there is likely more "smarts" we could add to this. + fn reduce_options_if_needed(&mut self) { + // We sort branches by those that have the most options first. + self.closed_branches + .sort_by(|b1, b2| b1.0.len().cmp(&b2.0.len()).reverse()); + let mut plan_count = self + .closed_branches + .iter() + .try_fold(1, |product, branch| { + if branch.0.is_empty() { + // This would correspond to not being to find *any* path + // for a particular queried field, + // which means we have no plan for the overall query. + // Now, this shouldn't happen in practice if composition validation + // has been run successfully (and is not buggy), + // since the goal of composition validation + // is exactly to ensure we can never run into this path. + // In any case, we will throw later if that happens, + // but let's just return the proper result here, which is no plan at all. + None + } else { + Some(product * branch.0.len()) + } + }) + .unwrap_or(0); + // debug!("Query has {plan_count} possible plans"); + + let max_evaluated_plans = + u32::from(self.parameters.config.debug.max_evaluated_plans) as usize; + loop { + // Note that if `self.closed_branches[0]` is our only branch, it's fine, + // we'll continue to remove options from it (but that is beyond unlikely). + let first_branch_len = self.closed_branches[0].0.len(); + if plan_count <= max_evaluated_plans || first_branch_len <= 1 { + break; + } + Self::prune_and_reorder_first_branch(&mut self.closed_branches); + plan_count -= plan_count / first_branch_len; + + // debug!("Reduced plans to consider to {plan_count} plans"); + } + } + + /// Removes the right-most option of the first branch and moves that branch to its new place + /// to keep them sorted by decreasing number of options. + /// Assumes that branches were already sorted that way, and that there is at least one branch. + /// + /// This takes a generic parameter instead of `&mut self` for unit-testing. + fn prune_and_reorder_first_branch(closed_branches: &mut [impl ClosedBranchLike]) { + let (first_branch, rest) = closed_branches.split_first_mut().unwrap(); + let first_branch_previous_len = first_branch.len(); + first_branch.pop(); + let to_jump_over = rest + .iter() + .take_while(|branch| branch.len() == first_branch_previous_len) + .count(); + if to_jump_over == 0 { + // No other branch has as many options as `closed_branches[0]` did, + // so removing one option still left `closed_branches` sorted. + } else { + // `closed_branches` now looks like this: + // + // | index | number of options in branch | + // | ---------------- | -------------------------------- | + // | 0 | first_branch_previous_len - 1 | + // | 1 | first_branch_previous_len | + // | … | first_branch_previous_len | + // | to_jump_over | first_branch_previous_len | + // | to_jump_over + 1 | <= first_branch_previous_len - 1 | + // | … | <= first_branch_previous_len - 1 | + // + // The range `closed_branches[1 ..= to_jump_over]` is branches + // that all have the same number of options, so they can be in any relative order. + + closed_branches.swap(0, to_jump_over) + + // `closed_branches` now looks like this, which is correctly sorted: + // + // | index | number of options in branch | + // | ---------------- | -------------------------------- | + // | 0 | first_branch_previous_len | + // | 1 | first_branch_previous_len | + // | … | first_branch_previous_len | + // | to_jump_over | first_branch_previous_len - 1 | + // | to_jump_over + 1 | <= first_branch_previous_len - 1 | + // | … | <= first_branch_previous_len - 1 | + } + } + + pub(crate) fn new_dependency_graph(&self) -> FetchDependencyGraph { + let root_type = if self.is_top_level && self.has_defers { + self.parameters + .supergraph_schema + .schema() + .root_operation(self.root_kind.into()) + .cloned() + // A root operation type has to be an object type + .map(|type_name| ObjectTypeDefinitionPosition { type_name }.into()) + } else { + None + }; + FetchDependencyGraph::new( + self.parameters.supergraph_schema.clone(), + self.parameters.federated_query_graph.clone(), + root_type, + self.starting_id_generation, + ) + } + + fn updated_dependency_graph( + &self, + dependency_graph: &mut FetchDependencyGraph, + path_tree: &OpPathTree, + ) -> Result<(), FederationError> { + let is_root_path_tree = matches!( + path_tree.graph.node_weight(path_tree.node)?.type_, + QueryGraphNodeType::FederatedRootType(_) + ); + if is_root_path_tree { + // The root of the pathTree is one of the "fake" root of the subgraphs graph, + // which belongs to no subgraph but points to each ones. + // So we "unpack" the first level of the tree to find out our top level groups + // (and initialize our stack). + // Note that we can safely ignore the triggers of that first level + // as it will all be free transition, and we know we cannot have conditions. + for child in &path_tree.childs { + let edge = child.edge.expect("The root edge should not be None"); + let (_source_node, target_node) = path_tree.graph.edge_endpoints(edge)?; + let target_node = path_tree.graph.node_weight(target_node)?; + let subgraph_name = &target_node.source; + let root_type = match &target_node.type_ { + QueryGraphNodeType::SchemaType(OutputTypeDefinitionPosition::Object( + object, + )) => object.clone().into(), + ty => { + return Err(FederationError::internal(format!( + "expected an object type for the root of a subgraph, found {ty}" + ))) + } + }; + let fetch_dependency_node = dependency_graph.get_or_create_root_node( + subgraph_name, + self.root_kind, + root_type, + )?; + compute_nodes_for_tree( + dependency_graph, + &child.tree, + fetch_dependency_node, + Default::default(), + Default::default(), + &Default::default(), + )?; + } + } else { + let query_graph_node = path_tree.graph.node_weight(path_tree.node)?; + let subgraph_name = &query_graph_node.source; + let root_type = match &query_graph_node.type_ { + QueryGraphNodeType::SchemaType(position) => position.clone().try_into()?, + QueryGraphNodeType::FederatedRootType(_) => { + return Err(FederationError::internal( + "unexpected FederatedRootType not at the start of an OpPathTree", + )) + } + }; + let fetch_dependency_node = dependency_graph.get_or_create_root_node( + subgraph_name, + self.root_kind, + root_type, + )?; + compute_nodes_for_tree( + dependency_graph, + path_tree, + fetch_dependency_node, + Default::default(), + Default::default(), + &Default::default(), + )?; + } + Ok(()) + } + + fn resolve_condition_plan( + &self, + edge: EdgeIndex, + // PORT_NOTE: The following parameters are not currently used. + _context: &OpGraphPathContext, + excluded_destinations: &ExcludedDestinations, + excluded_conditions: &ExcludedConditions, + ) -> Result { + let graph = &self.parameters.federated_query_graph; + let head = graph.edge_endpoints(edge)?.0; + // Note: `QueryPlanningTraversal::resolve` method asserts that the edge has conditions before + // calling this method. + let edge_conditions = graph + .edge_weight(edge)? + .conditions + .as_ref() + .unwrap() + .as_ref(); + let parameters = QueryPlanningParameters { + head, + head_must_be_root: graph.node_weight(head)?.is_root_node(), + // otherwise, the same as self.parameters + // TODO: Some fields are deep-cloned here. We might want to revisit how they should be defined. + supergraph_schema: self.parameters.supergraph_schema.clone(), + federated_query_graph: graph.clone(), + operation: self.parameters.operation.clone(), + processor: self.parameters.processor.clone(), + abstract_types_with_inconsistent_runtime_types: self + .parameters + .abstract_types_with_inconsistent_runtime_types + .clone(), + config: self.parameters.config.clone(), + statistics: self.parameters.statistics.clone(), + }; + let best_plan_opt = QueryPlanningTraversal::new_inner( + ¶meters, + edge_conditions.clone(), + self.starting_id_generation, + self.has_defers, + self.root_kind, + self.cost_processor, + Default::default(), + excluded_destinations.clone(), + excluded_conditions.add_item(edge_conditions), + )? + .find_best_plan()?; + match best_plan_opt { + Some(best_plan) => Ok(ConditionResolution::Satisfied { + cost: best_plan.cost, + path_tree: Some(best_plan.path_tree), + }), + None => Ok(ConditionResolution::unsatisfied_conditions()), + } + } +} + +impl PlanBuilder> for QueryPlanningTraversal<'_> { + fn add_to_plan(&mut self, plan_info: &PlanInfo, tree: Arc) -> PlanInfo { + let mut updated_graph = plan_info.fetch_dependency_graph.clone(); + let result = self.updated_dependency_graph(&mut updated_graph, &tree); + if result.is_ok() { + PlanInfo { + fetch_dependency_graph: updated_graph, + path_tree: plan_info.path_tree.merge(&tree), + } + } else { + // Failed to update. Return the original plan. + PlanInfo { + fetch_dependency_graph: updated_graph, + path_tree: plan_info.path_tree.clone(), + } + } + } + + fn compute_plan_cost( + &mut self, + plan_info: &mut PlanInfo, + ) -> Result { + self.cost(&mut plan_info.fetch_dependency_graph) + } + + fn on_plan_generated( + &self, + _plan_info: &PlanInfo, + _cost: QueryPlanCost, + _prev_cost: Option, + ) { + // debug log + // if prev_cost.is_none() { + // print!("Computed plan with cost {}: {}", cost, plan_tree); + // } else if cost > prev_cost.unwrap() { + // print!( + // "Ignoring plan with cost {} (a better plan with cost {} exists): {}", + // cost, + // prev_cost.unwrap(), + // plan_tree + // ); + // } else { + // print!( + // "Found better with cost {} (previous had cost {}): {}", + // cost, + // prev_cost.unwrap(), + // plan_tree + // ); + // } + } +} + +// PORT_NOTE: In JS version, QueryPlanningTraversal has `conditionResolver` field, which +// is a closure calling `this.resolveConditionPlan` (`this` is captured here). +// The same would be infeasible to implement in Rust due to the cyclic references. +// Thus, instead of `condition_resolver` field, QueryPlanningTraversal was made to +// implement `ConditionResolver` trait along with `resolver_cache` field. +impl<'a> ConditionResolver for QueryPlanningTraversal<'a> { + /// A query plan resolver for edge conditions that caches the outcome per edge. + fn resolve( + &mut self, + edge: EdgeIndex, + context: &OpGraphPathContext, + excluded_destinations: &ExcludedDestinations, + excluded_conditions: &ExcludedConditions, + ) -> Result { + // Invariant check: The edge must have conditions. + let graph = &self.parameters.federated_query_graph; + let edge_data = graph.edge_weight(edge)?; + assert!( + edge_data.conditions.is_some(), + "Should not have been called for edge without conditions" + ); + + let cache_result = + self.resolver_cache + .contains(edge, context, excluded_destinations, excluded_conditions); + + if let ConditionResolutionCacheResult::Hit(cached_resolution) = cache_result { + return Ok(cached_resolution); + } + + let resolution = + self.resolve_condition_plan(edge, context, excluded_destinations, excluded_conditions)?; + // See if this resolution is eligible to be inserted into the cache. + if cache_result.is_miss() { + self.resolver_cache + .insert(edge, resolution.clone(), excluded_destinations.clone()); + } + Ok(resolution) + } +} + +trait ClosedBranchLike { + fn len(&self) -> usize; + fn pop(&mut self); +} + +impl ClosedBranchLike for ClosedBranch { + fn len(&self) -> usize { + self.0.len() + } + + fn pop(&mut self) { + self.0.pop(); + } +} + +#[cfg(test)] +impl ClosedBranchLike for String { + fn len(&self) -> usize { + self.len() + } + + fn pop(&mut self) { + self.pop(); + } +} + +#[test] +fn test_prune_and_reorder_first_branch() { + #[track_caller] + fn assert(branches: &[&str], expected: &[&str]) { + let mut branches: Vec<_> = branches.iter().map(|s| s.to_string()).collect(); + QueryPlanningTraversal::prune_and_reorder_first_branch(&mut branches); + assert_eq!(branches, expected) + } + // Either the first branch had strictly more options than the second, + // so it is still at its correct potition after removing one option… + assert( + &["abcdE", "fgh", "ijk", "lmn", "op"], + &["abcd", "fgh", "ijk", "lmn", "op"], + ); + assert( + &["abcD", "fgh", "ijk", "lmn", "op"], + &["abc", "fgh", "ijk", "lmn", "op"], + ); + assert(&["abcD", "fgh"], &["abc", "fgh"]); + assert(&["abcD"], &["abc"]); + + // … or, removing exactly one option from the first branch causes it + // to now have one less option (in this example: two options) + // than the second branch (here: three options) + // There is no other possibility with branches correctly sorted + // before calling `prune_and_reorder_first_branch`. + // + // There may be a run of consecutive branches (here: three branches) + // with equal number of options (here: three options each). + // Those branches can be in any relative order. + // We take advantage of that and swap the now-incorrectly-placed first branch + // with the last of this run: + assert( + &["abC", "fgh", "ijk", "lmn", "op"], + &["lmn", "fgh", "ijk", "ab", "op"], + ); + assert(&["abC", "fgh", "ijk", "lmn"], &["lmn", "fgh", "ijk", "ab"]); + // The "run" can be a single branch: + assert(&["abC", "lmn", "op"], &["lmn", "ab", "op"]); + assert(&["abC", "lmn"], &["lmn", "ab"]); +} diff --git a/apollo-federation/src/schema/argument_composition_strategies.rs b/apollo-federation/src/schema/argument_composition_strategies.rs new file mode 100644 index 0000000000..93499fcc6c --- /dev/null +++ b/apollo-federation/src/schema/argument_composition_strategies.rs @@ -0,0 +1,306 @@ +use apollo_compiler::ast::Value; +use apollo_compiler::name; +use apollo_compiler::schema::Type; +use lazy_static::lazy_static; + +use crate::schema::FederationSchema; + +#[derive(Clone, Copy)] +pub(crate) enum ArgumentCompositionStrategy { + Max, + Min, + Sum, + Intersection, + Union, +} + +lazy_static! { + pub(crate) static ref MAX_STRATEGY: MaxArgumentCompositionStrategy = + MaxArgumentCompositionStrategy::new(); + pub(crate) static ref MIN_STRATEGY: MinArgumentCompositionStrategy = + MinArgumentCompositionStrategy::new(); + pub(crate) static ref SUM_STRATEGY: SumArgumentCompositionStrategy = + SumArgumentCompositionStrategy::new(); + pub(crate) static ref INTERSECTION_STRATEGY: IntersectionArgumentCompositionStrategy = + IntersectionArgumentCompositionStrategy {}; + pub(crate) static ref UNION_STRATEGY: UnionArgumentCompositionStrategy = + UnionArgumentCompositionStrategy {}; +} + +impl ArgumentCompositionStrategy { + pub fn name(&self) -> &str { + match self { + Self::Max => MAX_STRATEGY.name(), + Self::Min => MIN_STRATEGY.name(), + Self::Sum => SUM_STRATEGY.name(), + Self::Intersection => INTERSECTION_STRATEGY.name(), + Self::Union => UNION_STRATEGY.name(), + } + } + + pub fn is_type_supported(&self, schema: &FederationSchema, ty: &Type) -> Result<(), String> { + match self { + Self::Max => MAX_STRATEGY.is_type_supported(schema, ty), + Self::Min => MIN_STRATEGY.is_type_supported(schema, ty), + Self::Sum => SUM_STRATEGY.is_type_supported(schema, ty), + Self::Intersection => INTERSECTION_STRATEGY.is_type_supported(schema, ty), + Self::Union => UNION_STRATEGY.is_type_supported(schema, ty), + } + } + + pub fn merge_values(&self, values: &[Value]) -> Value { + match self { + Self::Max => MAX_STRATEGY.merge_values(values), + Self::Min => MIN_STRATEGY.merge_values(values), + Self::Sum => SUM_STRATEGY.merge_values(values), + Self::Intersection => INTERSECTION_STRATEGY.merge_values(values), + Self::Union => UNION_STRATEGY.merge_values(values), + } + } + + pub fn from_str(s: &str) -> Option { + match s { + "MAX" => Some(Self::Max), + "MIN" => Some(Self::Min), + "SUM" => Some(Self::Sum), + "INTERSECTION" => Some(Self::Intersection), + "UNION" => Some(Self::Union), + _ => None, + } + } +} + +////////////////////////////////////////////////////////////////////////////// +// Implementation of ArgumentCompositionStrategy's + +/// Argument composition strategy for directives. +/// +/// Determines how to compose the merged argument value from directive +/// applications across subgraph schemas. +pub(crate) trait ArgumentComposition { + /// The name of the validator. + fn name(&self) -> &str; + /// Is the type `ty` supported by this validator? + fn is_type_supported(&self, schema: &FederationSchema, ty: &Type) -> Result<(), String>; + /// Assumes that schemas are validated by `is_type_supported`. + fn merge_values(&self, values: &[Value]) -> Value; +} + +#[derive(Clone)] +pub(crate) struct FixedTypeSupportValidator { + pub supported_types: Vec, +} + +impl FixedTypeSupportValidator { + fn is_type_supported(&self, _schema: &FederationSchema, ty: &Type) -> Result<(), String> { + let is_supported = self + .supported_types + .iter() + .any(|supported_ty| *supported_ty == *ty); + if is_supported { + return Ok(()); + } + + let expected_types: Vec = self + .supported_types + .iter() + .map(|ty| ty.to_string()) + .collect(); + Err(format!("type(s) {}", expected_types.join(", "))) + } +} + +fn int_type() -> Type { + Type::Named(name!("Int")) +} + +fn support_any_non_null_array(ty: &Type) -> Result<(), String> { + if !ty.is_non_null() || !ty.is_list() { + Err("non-nullable list types of any type".to_string()) + } else { + Ok(()) + } +} + +// MAX +#[derive(Clone)] +pub(crate) struct MaxArgumentCompositionStrategy { + validator: FixedTypeSupportValidator, +} + +impl MaxArgumentCompositionStrategy { + fn new() -> Self { + Self { + validator: FixedTypeSupportValidator { + supported_types: vec![int_type().non_null()], + }, + } + } +} + +impl ArgumentComposition for MaxArgumentCompositionStrategy { + fn name(&self) -> &str { + "MAX" + } + + fn is_type_supported(&self, schema: &FederationSchema, ty: &Type) -> Result<(), String> { + self.validator.is_type_supported(schema, ty) + } + + // TODO: check if this neeeds to be an Result to avoid the panic!() + // https://apollographql.atlassian.net/browse/FED-170 + fn merge_values(&self, values: &[Value]) -> Value { + values + .iter() + .map(|val| match val { + Value::Int(i) => i.try_to_i32().unwrap(), + _ => panic!("Unexpected value type"), + }) + .max() + .unwrap_or_default() + .into() + } +} + +// MIN +#[derive(Clone)] +pub(crate) struct MinArgumentCompositionStrategy { + validator: FixedTypeSupportValidator, +} + +impl MinArgumentCompositionStrategy { + fn new() -> Self { + Self { + validator: FixedTypeSupportValidator { + supported_types: vec![int_type().non_null()], + }, + } + } +} + +impl ArgumentComposition for MinArgumentCompositionStrategy { + fn name(&self) -> &str { + "MIN" + } + + fn is_type_supported(&self, schema: &FederationSchema, ty: &Type) -> Result<(), String> { + self.validator.is_type_supported(schema, ty) + } + + // TODO: check if this neeeds to be an Result to avoid the panic!() + // https://apollographql.atlassian.net/browse/FED-170 + fn merge_values(&self, values: &[Value]) -> Value { + values + .iter() + .map(|val| match val { + Value::Int(i) => i.try_to_i32().unwrap(), + _ => panic!("Unexpected value type"), + }) + .min() + .unwrap_or_default() + .into() + } +} + +// SUM +#[derive(Clone)] +pub(crate) struct SumArgumentCompositionStrategy { + validator: FixedTypeSupportValidator, +} + +impl SumArgumentCompositionStrategy { + fn new() -> Self { + Self { + validator: FixedTypeSupportValidator { + supported_types: vec![int_type().non_null()], + }, + } + } +} + +impl ArgumentComposition for SumArgumentCompositionStrategy { + fn name(&self) -> &str { + "SUM" + } + + fn is_type_supported(&self, schema: &FederationSchema, ty: &Type) -> Result<(), String> { + self.validator.is_type_supported(schema, ty) + } + + // TODO: check if this neeeds to be an Result to avoid the panic!() + // https://apollographql.atlassian.net/browse/FED-170 + fn merge_values(&self, values: &[Value]) -> Value { + values + .iter() + .map(|val| match val { + Value::Int(i) => i.try_to_i32().unwrap(), + _ => panic!("Unexpected value type"), + }) + .sum::() + .into() + } +} + +// INTERSECTION +#[derive(Clone)] +pub(crate) struct IntersectionArgumentCompositionStrategy {} + +impl ArgumentComposition for IntersectionArgumentCompositionStrategy { + fn name(&self) -> &str { + "INTERSECTION" + } + + fn is_type_supported(&self, _schema: &FederationSchema, ty: &Type) -> Result<(), String> { + support_any_non_null_array(ty) + } + + // TODO: check if this neeeds to be an Result to avoid the panic!() + // https://apollographql.atlassian.net/browse/FED-170 + fn merge_values(&self, values: &[Value]) -> Value { + // Each item in `values` must be a Value::List(...). + values + .split_first() + .map(|(first, rest)| { + let first_ls = first.as_list().unwrap(); + // Not a super efficient implementation, but we don't expect large problem sizes. + let mut result = first_ls.to_vec(); + for val in rest { + let val_ls = val.as_list().unwrap(); + result.retain(|result_item| val_ls.iter().any(|n| *n == *result_item)); + } + Value::List(result) + }) + .unwrap() + } +} + +// UNION +#[derive(Clone)] +pub(crate) struct UnionArgumentCompositionStrategy {} + +impl ArgumentComposition for UnionArgumentCompositionStrategy { + fn name(&self) -> &str { + "UNION" + } + + fn is_type_supported(&self, _schema: &FederationSchema, ty: &Type) -> Result<(), String> { + support_any_non_null_array(ty) + } + + // TODO: check if this neeeds to be an Result to avoid the panic!() + // https://apollographql.atlassian.net/browse/FED-170 + fn merge_values(&self, values: &[Value]) -> Value { + // Each item in `values` must be a Value::List(...). + // Not a super efficient implementation, but we don't expect large problem sizes. + let mut result = Vec::new(); + for val in values { + let val_ls = val.as_list().unwrap(); + for x in val_ls.iter() { + if !result.contains(x) { + result.push(x.clone()); + } + } + } + Value::List(result) + } +} diff --git a/apollo-federation/src/schema/definitions.rs b/apollo-federation/src/schema/definitions.rs new file mode 100644 index 0000000000..b35d5c2a1b --- /dev/null +++ b/apollo-federation/src/schema/definitions.rs @@ -0,0 +1,74 @@ +use apollo_compiler::ast::NamedType; +use apollo_compiler::ast::Type; +use apollo_compiler::Schema; + +use crate::error::FederationError; +use crate::error::SingleFederationError; +use crate::schema::position::CompositeTypeDefinitionPosition; +use crate::schema::position::InterfaceTypeDefinitionPosition; +use crate::schema::position::TypeDefinitionPosition; +use crate::schema::position::UnionTypeDefinitionPosition; + +pub(crate) enum AbstractType { + Interface(InterfaceTypeDefinitionPosition), + Union(UnionTypeDefinitionPosition), +} + +impl From for CompositeTypeDefinitionPosition { + fn from(value: AbstractType) -> Self { + match value { + AbstractType::Interface(x) => Self::Interface(x), + AbstractType::Union(x) => Self::Union(x), + } + } +} + +pub(crate) fn is_abstract_type(ty: TypeDefinitionPosition) -> bool { + matches!( + ty, + crate::schema::position::TypeDefinitionPosition::Interface(_) + | crate::schema::position::TypeDefinitionPosition::Union(_) + ) +} + +pub(crate) fn is_composite_type(ty: &NamedType, schema: &Schema) -> Result { + Ok(matches!( + schema + .types + .get(ty) + .ok_or_else(|| SingleFederationError::Internal { + message: format!("Cannot find type `'{}\'", ty), + })?, + apollo_compiler::schema::ExtendedType::Object(_) + | apollo_compiler::schema::ExtendedType::Interface(_) + | apollo_compiler::schema::ExtendedType::Union(_) + )) +} + +/** + * This essentially follows the beginning of https://spec.graphql.org/draft/#SameResponseShape(). + * That is, the types cannot be merged unless: + * - they have the same nullability and "list-ability", potentially recursively. + * - their base type is either both composite, or are the same type. + */ +pub(crate) fn types_can_be_merged( + t1: &Type, + t2: &Type, + schema: &Schema, +) -> Result { + let (n1, n2) = match (t1, t2) { + (Type::Named(n1), Type::Named(n2)) | (Type::NonNullNamed(n1), Type::NonNullNamed(n2)) => { + (n1, n2) + } + (Type::List(inner1), Type::List(inner2)) + | (Type::NonNullList(inner1), Type::NonNullList(inner2)) => { + return types_can_be_merged(inner1, inner2, schema) + } + _ => return Ok(false), + }; + if is_composite_type(n1, schema)? { + return is_composite_type(n2, schema); + } + + Ok(n1 == n2) +} diff --git a/apollo-federation/src/schema/field_set.rs b/apollo-federation/src/schema/field_set.rs new file mode 100644 index 0000000000..47866c3f47 --- /dev/null +++ b/apollo-federation/src/schema/field_set.rs @@ -0,0 +1,242 @@ +use apollo_compiler::executable; +use apollo_compiler::executable::FieldSet; +use apollo_compiler::schema::ExtendedType; +use apollo_compiler::schema::NamedType; +use apollo_compiler::validation::Valid; +use apollo_compiler::NodeStr; +use apollo_compiler::Schema; +use indexmap::IndexMap; + +use crate::error::FederationError; +use crate::error::MultipleFederationErrors; +use crate::error::SingleFederationError; +use crate::query_plan::operation::NamedFragments; +use crate::query_plan::operation::SelectionSet; +use crate::schema::position::CompositeTypeDefinitionPosition; +use crate::schema::position::FieldDefinitionPosition; +use crate::schema::position::InterfaceTypeDefinitionPosition; +use crate::schema::position::ObjectTypeDefinitionPosition; +use crate::schema::position::UnionTypeDefinitionPosition; +use crate::schema::FederationSchema; +use crate::schema::ValidFederationSchema; + +// Federation spec does not allow the alias syntax in field set strings. +// However, since `parse_field_set` uses the standard GraphQL parser, which allows aliases, +// we need this secondary check to ensure that aliases are not used. +fn check_absence_of_aliases( + field_set: &Valid
, + code_str: &str, +) -> Result<(), FederationError> { + let aliases = field_set.selection_set.fields().filter_map(|field| { + field.alias.as_ref().map(|alias| + SingleFederationError::UnsupportedFeature { + // PORT_NOTE: The JS version also quotes the directive name in the error message. + // For example, "aliases are not currently supported in @requires". + message: format!( + r#"Cannot use alias "{}" in "{}": aliases are not currently supported in the used directive"#, + alias, code_str) + }) + }); + MultipleFederationErrors::from_iter(aliases).into_result() +} + +// TODO: In the JS codebase, this has some error-rewriting to help give the user better hints around +// non-existent fields. +pub(crate) fn parse_field_set( + schema: &ValidFederationSchema, + parent_type_name: NamedType, + value: &str, +) -> Result { + // Note this parsing takes care of adding curly braces ("{" and "}") if they aren't in the + // string. + let field_set = FieldSet::parse_and_validate( + schema.schema(), + parent_type_name, + value, + "field_set.graphql", + )?; + + // Validate the field set has no aliases. + check_absence_of_aliases(&field_set, value)?; + + // field set should not contain any named fragments + let named_fragments = NamedFragments::new(&IndexMap::new(), schema); + SelectionSet::from_selection_set(&field_set.selection_set, &named_fragments, schema) +} + +/// This exists because there's a single callsite in extract_subgraphs_from_supergraph() that needs +/// to parse field sets before the schema has finished building. Outside that case, you should +/// always use `parse_field_set()` instead. +// TODO: As noted in the single callsite, ideally we could move the parsing to after extraction, but +// it takes time to determine whether that impacts correctness, so we're leaving it for later. +pub(crate) fn parse_field_set_without_normalization( + schema: &Valid, + parent_type_name: NamedType, + value: &str, +) -> Result { + // Note this parsing takes care of adding curly braces ("{" and "}") if they aren't in the + // string. + let field_set = + FieldSet::parse_and_validate(schema, parent_type_name, value, "field_set.graphql")?; + Ok(field_set.into_inner().selection_set) +} + +// PORT_NOTE: The JS codebase called this `collectTargetFields()`, but this naming didn't make it +// apparent that this was collecting from a field set, so we've renamed it accordingly. Note that +// the JS function also optionally collected interface field implementations, but we've split that +// off into a separate function. +pub(crate) fn collect_target_fields_from_field_set( + schema: &Valid, + parent_type_name: NamedType, + value: NodeStr, +) -> Result, FederationError> { + // Note this parsing takes care of adding curly braces ("{" and "}") if they aren't in the + // string. + let field_set = FieldSet::parse_and_validate( + schema, + parent_type_name, + value.as_str(), + "field_set.graphql", + )?; + let mut stack = vec![&field_set.selection_set]; + let mut fields = vec![]; + while let Some(selection_set) = stack.pop() { + let Some(parent_type) = schema.types.get(&selection_set.ty) else { + return Err(FederationError::internal( + "Unexpectedly missing selection set type from schema.", + )); + }; + let parent_type_position: CompositeTypeDefinitionPosition = match parent_type { + ExtendedType::Object(_) => ObjectTypeDefinitionPosition { + type_name: selection_set.ty.clone(), + } + .into(), + ExtendedType::Interface(_) => InterfaceTypeDefinitionPosition { + type_name: selection_set.ty.clone(), + } + .into(), + ExtendedType::Union(_) => UnionTypeDefinitionPosition { + type_name: selection_set.ty.clone(), + } + .into(), + _ => { + return Err(FederationError::internal( + "Unexpectedly encountered non-composite type for selection set.", + )); + } + }; + // The stack iterates through what we push in reverse order, so we iterate through + // selections in reverse order to fix it. + for selection in selection_set.selections.iter().rev() { + match selection { + executable::Selection::Field(field) => { + fields.push(parent_type_position.field(field.name.clone())?); + if !field.selection_set.selections.is_empty() { + stack.push(&field.selection_set); + } + } + executable::Selection::FragmentSpread(_) => { + return Err(FederationError::internal( + "Unexpectedly encountered fragment spread in FieldSet.", + )); + } + executable::Selection::InlineFragment(inline_fragment) => { + stack.push(&inline_fragment.selection_set); + } + } + } + } + Ok(fields) +} + +// PORT_NOTE: This is meant as a companion function for collect_target_fields_from_field_set(), as +// some callers will also want to include interface field implementations. +pub(crate) fn add_interface_field_implementations( + fields: Vec, + schema: &FederationSchema, +) -> Result, FederationError> { + let mut new_fields = vec![]; + for field in fields { + let interface_field = if let FieldDefinitionPosition::Interface(field) = &field { + Some(field.clone()) + } else { + None + }; + new_fields.push(field); + if let Some(interface_field) = interface_field { + for implementing_type in &schema + .referencers + .get_interface_type(&interface_field.type_name)? + .object_types + { + new_fields.push( + implementing_type + .field(interface_field.field_name.clone()) + .into(), + ); + } + } + } + Ok(new_fields) +} + +#[cfg(test)] +mod tests { + use apollo_compiler::schema::Name; + + use crate::error::FederationError; + use crate::query_graph::build_federated_query_graph; + use crate::subgraph::Subgraph; + use crate::Supergraph; + + #[test] + fn test_aliases_in_field_set() -> Result<(), FederationError> { + let sdl = r#" + type Query { + a: Int! @requires(fields: "r1: r") + r: Int! @external + } + "#; + + let subgraph = Subgraph::parse_and_expand("S1", "http://S1", sdl).unwrap(); + let supergraph = Supergraph::compose([&subgraph].to_vec()).unwrap(); + let err = super::parse_field_set(&supergraph.schema, Name::new("Query").unwrap(), "r1: r") + .map(|_| "Unexpected success") // ignore the Ok value + .expect_err("Expected alias error"); + assert_eq!( + err.to_string(), + r#"Cannot use alias "r1" in "r1: r": aliases are not currently supported in the used directive"# + ); + Ok(()) + } + + #[test] + fn test_aliases_in_field_set_via_build_federated_query_graph() -> Result<(), FederationError> { + // NB: This tests multiple alias errors in the same field set. + let sdl = r#" + type Query { + a: Int! @requires(fields: "r1: r s q1: q") + r: Int! @external + s: String! @external + q: String! @external + } + "#; + + let subgraph = Subgraph::parse_and_expand("S1", "http://S1", sdl).unwrap(); + let supergraph = Supergraph::compose([&subgraph].to_vec()).unwrap(); + let api_schema = supergraph.to_api_schema(Default::default())?; + // Testing via `build_federated_query_graph` function, which validates the @requires directive. + let err = build_federated_query_graph(supergraph.schema, api_schema, None, None) + .map(|_| "Unexpected success") // ignore the Ok value + .expect_err("Expected alias error"); + assert_eq!( + err.to_string(), + r#"The following errors occurred: + + - Cannot use alias "r1" in "r1: r s q1: q": aliases are not currently supported in the used directive + + - Cannot use alias "q1" in "r1: r s q1: q": aliases are not currently supported in the used directive"# + ); + Ok(()) + } +} diff --git a/apollo-federation/src/schema/mod.rs b/apollo-federation/src/schema/mod.rs new file mode 100644 index 0000000000..9f3dd35867 --- /dev/null +++ b/apollo-federation/src/schema/mod.rs @@ -0,0 +1,314 @@ +use std::hash::Hash; +use std::hash::Hasher; +use std::ops::Deref; +use std::sync::Arc; + +use apollo_compiler::schema::ExtendedType; +use apollo_compiler::schema::Name; +use apollo_compiler::validation::Valid; +use apollo_compiler::Schema; +use indexmap::IndexSet; +use referencer::Referencers; + +use crate::error::FederationError; +use crate::error::SingleFederationError; +use crate::link::federation_spec_definition::get_federation_spec_definition_from_subgraph; +use crate::link::federation_spec_definition::FEDERATION_ENTITY_TYPE_NAME_IN_SPEC; +use crate::link::LinksMetadata; +use crate::schema::position::CompositeTypeDefinitionPosition; +use crate::schema::position::DirectiveDefinitionPosition; +use crate::schema::position::EnumTypeDefinitionPosition; +use crate::schema::position::InputObjectTypeDefinitionPosition; +use crate::schema::position::InterfaceTypeDefinitionPosition; +use crate::schema::position::ObjectTypeDefinitionPosition; +use crate::schema::position::ScalarTypeDefinitionPosition; +use crate::schema::position::TypeDefinitionPosition; +use crate::schema::position::UnionTypeDefinitionPosition; +use crate::schema::subgraph_metadata::SubgraphMetadata; + +pub(crate) mod argument_composition_strategies; +pub(crate) mod definitions; +pub(crate) mod field_set; +pub(crate) mod position; +pub(crate) mod referencer; +pub(crate) mod subgraph_metadata; + +fn compute_subgraph_metadata( + schema: &Valid, +) -> Result, FederationError> { + Ok( + if let Ok(federation_spec_definition) = get_federation_spec_definition_from_subgraph(schema) + { + Some(SubgraphMetadata::new(schema, federation_spec_definition)?) + } else { + None + }, + ) +} +pub(crate) mod type_and_directive_specification; + +/// A GraphQL schema with federation data. +#[derive(Debug)] +pub struct FederationSchema { + schema: Schema, + referencers: Referencers, + links_metadata: Option>, + /// This is only populated for valid subgraphs, and can only be accessed if you have a + /// `ValidFederationSchema`. + subgraph_metadata: Option>, +} + +impl FederationSchema { + pub(crate) fn schema(&self) -> &Schema { + &self.schema + } + + pub(crate) fn schema_mut(&mut self) -> &mut Schema { + &mut self.schema + } + + /// Discard the Federation metadata and return the apollo-compiler schema. + pub fn into_inner(self) -> Schema { + self.schema + } + + pub(crate) fn metadata(&self) -> Option<&LinksMetadata> { + self.links_metadata.as_deref() + } + + pub(crate) fn referencers(&self) -> &Referencers { + &self.referencers + } + + /// Returns all the types in the schema, minus builtins. + pub(crate) fn get_types(&self) -> impl Iterator + '_ { + self.schema + .types + .iter() + .filter(|(_, ty)| !ty.is_built_in()) + .map(|(type_name, type_)| { + let type_name = type_name.clone(); + match type_ { + ExtendedType::Scalar(_) => ScalarTypeDefinitionPosition { type_name }.into(), + ExtendedType::Object(_) => ObjectTypeDefinitionPosition { type_name }.into(), + ExtendedType::Interface(_) => { + InterfaceTypeDefinitionPosition { type_name }.into() + } + ExtendedType::Union(_) => UnionTypeDefinitionPosition { type_name }.into(), + ExtendedType::Enum(_) => EnumTypeDefinitionPosition { type_name }.into(), + ExtendedType::InputObject(_) => { + InputObjectTypeDefinitionPosition { type_name }.into() + } + } + }) + } + + pub(crate) fn get_directive_definitions( + &self, + ) -> impl Iterator + '_ { + self.schema + .directive_definitions + .keys() + .map(|name| DirectiveDefinitionPosition { + directive_name: name.clone(), + }) + } + + pub(crate) fn get_type( + &self, + type_name: Name, + ) -> Result { + let type_ = + self.schema + .types + .get(&type_name) + .ok_or_else(|| SingleFederationError::Internal { + message: format!("Schema has no type \"{}\"", type_name), + })?; + Ok(match type_ { + ExtendedType::Scalar(_) => ScalarTypeDefinitionPosition { type_name }.into(), + ExtendedType::Object(_) => ObjectTypeDefinitionPosition { type_name }.into(), + ExtendedType::Interface(_) => InterfaceTypeDefinitionPosition { type_name }.into(), + ExtendedType::Union(_) => UnionTypeDefinitionPosition { type_name }.into(), + ExtendedType::Enum(_) => EnumTypeDefinitionPosition { type_name }.into(), + ExtendedType::InputObject(_) => InputObjectTypeDefinitionPosition { type_name }.into(), + }) + } + + pub(crate) fn try_get_type(&self, type_name: Name) -> Option { + self.get_type(type_name).ok() + } + + pub(crate) fn possible_runtime_types( + &self, + composite_type_definition_position: CompositeTypeDefinitionPosition, + ) -> Result, FederationError> { + Ok(match composite_type_definition_position { + CompositeTypeDefinitionPosition::Object(pos) => IndexSet::from([pos]), + CompositeTypeDefinitionPosition::Interface(pos) => self + .referencers() + .get_interface_type(&pos.type_name)? + .object_types + .clone(), + CompositeTypeDefinitionPosition::Union(pos) => pos + .get(self.schema())? + .members + .iter() + .map(|t| ObjectTypeDefinitionPosition { + type_name: t.name.clone(), + }) + .collect::>(), + }) + } + + pub(crate) fn validate(self) -> Result { + self.validate_or_return_self().map_err(|e| e.1) + } + + /// Similar to `Self::validate` but returns `self` as part of the error should it be needed by + /// the caller + pub(crate) fn validate_or_return_self( + mut self, + ) -> Result { + let schema = match self.schema.validate() { + Ok(schema) => schema.into_inner(), + Err(e) => { + self.schema = e.partial; + return Err((self, e.errors.into())); + } + }; + ValidFederationSchema::new_assume_valid(FederationSchema { schema, ..self }) + } + + pub(crate) fn assume_valid(self) -> Result { + ValidFederationSchema::new_assume_valid(self).map_err(|(_schema, error)| error) + } + + pub(crate) fn get_directive_definition( + &self, + name: &Name, + ) -> Option { + self.schema + .directive_definitions + .contains_key(name) + .then(|| DirectiveDefinitionPosition { + directive_name: name.clone(), + }) + } + + /// Note that a subgraph may have no "entities" and so no `_Entity` type. + pub(crate) fn entity_type( + &self, + ) -> Result, FederationError> { + // Note that the _Entity type is special in that: + // 1. Spec renaming doesn't take place for it (there's no prefixing or importing needed), + // in order to maintain backwards compatibility with Fed 1. + // 2. Its presence is optional; if absent, it means the subgraph has no resolvable keys. + match self.schema.types.get(&FEDERATION_ENTITY_TYPE_NAME_IN_SPEC) { + Some(ExtendedType::Union(_)) => Ok(Some(UnionTypeDefinitionPosition { + type_name: FEDERATION_ENTITY_TYPE_NAME_IN_SPEC, + })), + Some(_) => Err(FederationError::internal(format!( + "Unexpectedly found non-union for federation spec's `{}` type definition", + FEDERATION_ENTITY_TYPE_NAME_IN_SPEC + ))), + None => Ok(None), + } + } +} + +/// A GraphQL schema with federation data that is known to be valid, and cheap to clone. +#[derive(Clone)] +pub struct ValidFederationSchema { + schema: Arc>, +} + +impl ValidFederationSchema { + pub fn new(schema: Valid) -> Result { + let schema = FederationSchema::new(schema.into_inner())?; + + Self::new_assume_valid(schema).map_err(|(_schema, error)| error) + } + + pub(crate) fn ptr_eq(&self, other: &Self) -> bool { + Arc::ptr_eq(&self.schema, &other.schema) + } + + /// Construct a ValidFederationSchema by assuming the given FederationSchema is valid. + fn new_assume_valid( + mut schema: FederationSchema, + ) -> Result { + // Populating subgraph metadata requires a mutable FederationSchema, while computing the subgraph + // metadata requires a valid FederationSchema. Since valid schemas are immutable, we have + // to jump through some hoops here. We already assume that `schema` is valid GraphQL, so we + // can temporarily create a `&Valid` to compute subgraph metadata, drop + // that reference to populate the metadata, and finally move the finished FederationSchema into + // the ValidFederationSchema instance. + let valid_schema = Valid::assume_valid_ref(&schema); + let subgraph_metadata = match compute_subgraph_metadata(valid_schema) { + Ok(metadata) => metadata.map(Box::new), + Err(err) => return Err((schema, err)), + }; + schema.subgraph_metadata = subgraph_metadata; + + let schema = Arc::new(Valid::assume_valid(schema)); + Ok(ValidFederationSchema { schema }) + } + + /// Access the GraphQL schema. + pub fn schema(&self) -> &Valid { + Valid::assume_valid_ref(&self.schema.schema) + } + + /// Returns subgraph-specific metadata. + /// + /// Returns `None` for supergraph schemas. + pub(crate) fn subgraph_metadata(&self) -> Option<&SubgraphMetadata> { + self.schema.subgraph_metadata.as_deref() + } + + pub(crate) fn federation_type_name_in_schema( + &self, + name: Name, + ) -> Result { + // Currently, the types used to define the federation operations, that is _Any, _Entity and _Service, + // are not considered part of the federation spec, and are instead hardcoded to the names above. + // The reason being that there is no way to maintain backward compatbility with fed2 if we were to add + // those to the federation spec without requiring users to add those types to their @link `import`, + // and that wouldn't be a good user experience (because most users don't really know what those types + // are/do). And so we special case it. + if name.starts_with('_') { + return Ok(name); + } + + todo!() + } +} + +impl Deref for ValidFederationSchema { + type Target = FederationSchema; + + fn deref(&self) -> &Self::Target { + &self.schema + } +} + +impl Eq for ValidFederationSchema {} + +impl PartialEq for ValidFederationSchema { + fn eq(&self, other: &ValidFederationSchema) -> bool { + Arc::ptr_eq(&self.schema, &other.schema) + } +} + +impl Hash for ValidFederationSchema { + fn hash(&self, state: &mut H) { + Arc::as_ptr(&self.schema).hash(state); + } +} + +impl std::fmt::Debug for ValidFederationSchema { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "ValidFederationSchema @ {:?}", Arc::as_ptr(&self.schema)) + } +} diff --git a/apollo-federation/src/schema/position.rs b/apollo-federation/src/schema/position.rs new file mode 100644 index 0000000000..e61d227614 --- /dev/null +++ b/apollo-federation/src/schema/position.rs @@ -0,0 +1,6524 @@ +use std::fmt::Debug; +use std::fmt::Display; +use std::fmt::Formatter; +use std::ops::Deref; + +use apollo_compiler::ast; +use apollo_compiler::name; +use apollo_compiler::schema::Component; +use apollo_compiler::schema::ComponentName; +use apollo_compiler::schema::Directive; +use apollo_compiler::schema::DirectiveDefinition; +use apollo_compiler::schema::EnumType; +use apollo_compiler::schema::EnumValueDefinition; +use apollo_compiler::schema::ExtendedType; +use apollo_compiler::schema::FieldDefinition; +use apollo_compiler::schema::InputObjectType; +use apollo_compiler::schema::InputValueDefinition; +use apollo_compiler::schema::InterfaceType; +use apollo_compiler::schema::Name; +use apollo_compiler::schema::ObjectType; +use apollo_compiler::schema::ScalarType; +use apollo_compiler::schema::SchemaDefinition; +use apollo_compiler::schema::UnionType; +use apollo_compiler::Node; +use apollo_compiler::Schema; +use indexmap::IndexSet; +use lazy_static::lazy_static; +use strum::IntoEnumIterator; + +use crate::error::FederationError; +use crate::error::SingleFederationError; +use crate::link::database::links_metadata; +use crate::link::federation_spec_definition::FEDERATION_INTERFACEOBJECT_DIRECTIVE_NAME_IN_SPEC; +use crate::link::spec_definition::SpecDefinition; +use crate::query_graph::QueryGraphNodeType; +use crate::schema::referencer::DirectiveReferencers; +use crate::schema::referencer::EnumTypeReferencers; +use crate::schema::referencer::InputObjectTypeReferencers; +use crate::schema::referencer::InterfaceTypeReferencers; +use crate::schema::referencer::ObjectTypeReferencers; +use crate::schema::referencer::Referencers; +use crate::schema::referencer::ScalarTypeReferencers; +use crate::schema::referencer::UnionTypeReferencers; +use crate::schema::FederationSchema; + +#[derive(Clone, PartialEq, Eq, Hash, derive_more::From, derive_more::Display)] +pub(crate) enum TypeDefinitionPosition { + Scalar(ScalarTypeDefinitionPosition), + Object(ObjectTypeDefinitionPosition), + Interface(InterfaceTypeDefinitionPosition), + Union(UnionTypeDefinitionPosition), + Enum(EnumTypeDefinitionPosition), + InputObject(InputObjectTypeDefinitionPosition), +} + +impl Debug for TypeDefinitionPosition { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::Scalar(p) => write!(f, "Scalar({p})"), + Self::Object(p) => write!(f, "Object({p})"), + Self::Interface(p) => write!(f, "Interface({p})"), + Self::Union(p) => write!(f, "Union({p})"), + Self::Enum(p) => write!(f, "Enum({p})"), + Self::InputObject(p) => write!(f, "InputObject({p})"), + } + } +} + +impl TypeDefinitionPosition { + pub(crate) fn type_name(&self) -> &Name { + match self { + TypeDefinitionPosition::Scalar(type_) => &type_.type_name, + TypeDefinitionPosition::Object(type_) => &type_.type_name, + TypeDefinitionPosition::Interface(type_) => &type_.type_name, + TypeDefinitionPosition::Union(type_) => &type_.type_name, + TypeDefinitionPosition::Enum(type_) => &type_.type_name, + TypeDefinitionPosition::InputObject(type_) => &type_.type_name, + } + } + + pub(crate) fn get<'schema>( + &self, + schema: &'schema Schema, + ) -> Result<&'schema ExtendedType, FederationError> { + let type_name = self.type_name(); + let type_ = schema + .types + .get(type_name) + .ok_or_else(|| SingleFederationError::Internal { + message: format!("Schema has no type \"{}\"", self), + })?; + let type_matches = match type_ { + ExtendedType::Scalar(_) => matches!(self, TypeDefinitionPosition::Scalar(_)), + ExtendedType::Object(_) => matches!(self, TypeDefinitionPosition::Object(_)), + ExtendedType::Interface(_) => matches!(self, TypeDefinitionPosition::Interface(_)), + ExtendedType::Union(_) => matches!(self, TypeDefinitionPosition::Union(_)), + ExtendedType::Enum(_) => matches!(self, TypeDefinitionPosition::Enum(_)), + ExtendedType::InputObject(_) => matches!(self, TypeDefinitionPosition::InputObject(_)), + }; + if type_matches { + Ok(type_) + } else { + Err(SingleFederationError::Internal { + message: format!("Schema type \"{}\" is the wrong kind", self), + } + .into()) + } + } + + pub(crate) fn try_get<'schema>( + &self, + schema: &'schema Schema, + ) -> Option<&'schema ExtendedType> { + self.get(schema).ok() + } +} + +#[derive(Clone, PartialEq, Eq, Hash, derive_more::From, derive_more::Display)] +pub(crate) enum OutputTypeDefinitionPosition { + Scalar(ScalarTypeDefinitionPosition), + Object(ObjectTypeDefinitionPosition), + Interface(InterfaceTypeDefinitionPosition), + Union(UnionTypeDefinitionPosition), + Enum(EnumTypeDefinitionPosition), +} + +impl Debug for OutputTypeDefinitionPosition { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::Scalar(p) => write!(f, "Scalar({p})"), + Self::Object(p) => write!(f, "Object({p})"), + Self::Interface(p) => write!(f, "Interface({p})"), + Self::Union(p) => write!(f, "Union({p})"), + Self::Enum(p) => write!(f, "Enum({p})"), + } + } +} + +impl OutputTypeDefinitionPosition { + pub(crate) fn type_name(&self) -> &Name { + match self { + OutputTypeDefinitionPosition::Scalar(type_) => &type_.type_name, + OutputTypeDefinitionPosition::Object(type_) => &type_.type_name, + OutputTypeDefinitionPosition::Interface(type_) => &type_.type_name, + OutputTypeDefinitionPosition::Union(type_) => &type_.type_name, + OutputTypeDefinitionPosition::Enum(type_) => &type_.type_name, + } + } + + pub(crate) fn get<'schema>( + &self, + schema: &'schema Schema, + ) -> Result<&'schema ExtendedType, FederationError> { + let ty = + schema + .types + .get(self.type_name()) + .ok_or_else(|| SingleFederationError::Internal { + message: format!("Schema has no type \"{}\"", self), + })?; + match (ty, self) { + (ExtendedType::Object(_), OutputTypeDefinitionPosition::Object(_)) + | (ExtendedType::Interface(_), OutputTypeDefinitionPosition::Interface(_)) + | (ExtendedType::Union(_), OutputTypeDefinitionPosition::Union(_)) + | (ExtendedType::Scalar(_), OutputTypeDefinitionPosition::Scalar(_)) + | (ExtendedType::Enum(_), OutputTypeDefinitionPosition::Enum(_)) => Ok(ty), + _ => Err(SingleFederationError::Internal { + message: format!("Schema type \"{}\" is the wrong kind", self), + } + .into()), + } + } + + pub(crate) fn try_get<'schema>( + &self, + schema: &'schema Schema, + ) -> Option<&'schema ExtendedType> { + self.get(schema).ok() + } +} + +impl TryFrom for OutputTypeDefinitionPosition { + type Error = FederationError; + + fn try_from(value: TypeDefinitionPosition) -> Result { + match value { + TypeDefinitionPosition::Scalar(value) => { + Ok(OutputTypeDefinitionPosition::Scalar(value)) + } + TypeDefinitionPosition::Object(value) => { + Ok(OutputTypeDefinitionPosition::Object(value)) + } + TypeDefinitionPosition::Interface(value) => { + Ok(OutputTypeDefinitionPosition::Interface(value)) + } + TypeDefinitionPosition::Union(value) => Ok(OutputTypeDefinitionPosition::Union(value)), + TypeDefinitionPosition::Enum(value) => Ok(OutputTypeDefinitionPosition::Enum(value)), + _ => Err(SingleFederationError::Internal { + message: format!("Type \"{}\" was unexpectedly not an output type", value,), + } + .into()), + } + } +} + +impl From for OutputTypeDefinitionPosition { + fn from(value: AbstractTypeDefinitionPosition) -> Self { + match value { + AbstractTypeDefinitionPosition::Interface(value) => { + OutputTypeDefinitionPosition::Interface(value) + } + AbstractTypeDefinitionPosition::Union(value) => { + OutputTypeDefinitionPosition::Union(value) + } + } + } +} + +#[derive(Clone, PartialEq, Eq, Hash, derive_more::From, derive_more::Display)] +pub(crate) enum CompositeTypeDefinitionPosition { + Object(ObjectTypeDefinitionPosition), + Interface(InterfaceTypeDefinitionPosition), + Union(UnionTypeDefinitionPosition), +} + +impl Debug for CompositeTypeDefinitionPosition { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::Object(p) => write!(f, "Object({p})"), + Self::Interface(p) => write!(f, "Interface({p})"), + Self::Union(p) => write!(f, "Union({p})"), + } + } +} + +impl CompositeTypeDefinitionPosition { + pub(crate) fn is_object_type(&self) -> bool { + matches!(self, CompositeTypeDefinitionPosition::Object(_)) + } + + pub(crate) fn is_interface_type(&self) -> bool { + matches!(self, CompositeTypeDefinitionPosition::Interface(_)) + } + + pub(crate) fn is_union_type(&self) -> bool { + matches!(self, CompositeTypeDefinitionPosition::Union(_)) + } + + pub(crate) fn is_abstract_type(&self) -> bool { + self.is_interface_type() || self.is_union_type() + } + + pub(crate) fn type_name(&self) -> &Name { + match self { + CompositeTypeDefinitionPosition::Object(type_) => &type_.type_name, + CompositeTypeDefinitionPosition::Interface(type_) => &type_.type_name, + CompositeTypeDefinitionPosition::Union(type_) => &type_.type_name, + } + } + + pub(crate) fn field( + &self, + field_name: Name, + ) -> Result { + match self { + CompositeTypeDefinitionPosition::Object(type_) => Ok(type_.field(field_name).into()), + CompositeTypeDefinitionPosition::Interface(type_) => Ok(type_.field(field_name).into()), + CompositeTypeDefinitionPosition::Union(type_) => { + let field = type_.introspection_typename_field(); + if *field.field_name() == field_name { + Ok(field.into()) + } else { + Err(SingleFederationError::Internal { + message: format!( + "Union types don't have field \"{}\", only \"{}\"", + field_name, + field.field_name(), + ), + } + .into()) + } + } + } + } + + pub(crate) fn introspection_typename_field(&self) -> FieldDefinitionPosition { + match self { + CompositeTypeDefinitionPosition::Object(type_) => { + type_.introspection_typename_field().into() + } + CompositeTypeDefinitionPosition::Interface(type_) => { + type_.introspection_typename_field().into() + } + CompositeTypeDefinitionPosition::Union(type_) => { + type_.introspection_typename_field().into() + } + } + } + + pub(crate) fn get<'schema>( + &self, + schema: &'schema Schema, + ) -> Result<&'schema ExtendedType, FederationError> { + let type_name = self.type_name(); + let type_ = schema + .types + .get(type_name) + .ok_or_else(|| SingleFederationError::Internal { + message: format!("Schema has no type \"{}\"", self), + })?; + let type_matches = match type_ { + ExtendedType::Object(_) => matches!(self, CompositeTypeDefinitionPosition::Object(_)), + ExtendedType::Interface(_) => { + matches!(self, CompositeTypeDefinitionPosition::Interface(_)) + } + ExtendedType::Union(_) => matches!(self, CompositeTypeDefinitionPosition::Union(_)), + _ => false, + }; + if type_matches { + Ok(type_) + } else { + Err(SingleFederationError::Internal { + message: format!("Schema type \"{}\" is the wrong kind", self), + } + .into()) + } + } + + pub(crate) fn try_get<'schema>( + &self, + schema: &'schema Schema, + ) -> Option<&'schema ExtendedType> { + self.get(schema).ok() + } + + pub(crate) fn is_interface_object_type(&self, schema: &Schema) -> bool { + let Ok(ExtendedType::Object(obj_type_def)) = self.get(schema) else { + return false; + }; + obj_type_def + .directives + .has(FEDERATION_INTERFACEOBJECT_DIRECTIVE_NAME_IN_SPEC.as_str()) + } +} + +impl TryFrom for CompositeTypeDefinitionPosition { + type Error = FederationError; + + fn try_from(value: TypeDefinitionPosition) -> Result { + match value { + TypeDefinitionPosition::Object(value) => { + Ok(CompositeTypeDefinitionPosition::Object(value)) + } + TypeDefinitionPosition::Interface(value) => { + Ok(CompositeTypeDefinitionPosition::Interface(value)) + } + TypeDefinitionPosition::Union(value) => { + Ok(CompositeTypeDefinitionPosition::Union(value)) + } + _ => Err(SingleFederationError::Internal { + message: format!(r#"Type "{value}" was unexpectedly not a composite type"#), + } + .into()), + } + } +} + +impl TryFrom for CompositeTypeDefinitionPosition { + type Error = FederationError; + + fn try_from(value: OutputTypeDefinitionPosition) -> Result { + match value { + OutputTypeDefinitionPosition::Object(value) => { + Ok(CompositeTypeDefinitionPosition::Object(value)) + } + OutputTypeDefinitionPosition::Interface(value) => { + Ok(CompositeTypeDefinitionPosition::Interface(value)) + } + OutputTypeDefinitionPosition::Union(value) => { + Ok(CompositeTypeDefinitionPosition::Union(value)) + } + _ => Err(FederationError::internal(format!( + "Type `{value}` was unexpectedly not a composite type" + ))), + } + } +} + +impl TryFrom for ObjectTypeDefinitionPosition { + type Error = FederationError; + + fn try_from(value: OutputTypeDefinitionPosition) -> Result { + match value { + OutputTypeDefinitionPosition::Object(value) => Ok(value), + _ => Err(FederationError::internal(format!( + "Type `{value}` was unexpectedly not an object type" + ))), + } + } +} + +impl From for CompositeTypeDefinitionPosition { + fn from(value: AbstractTypeDefinitionPosition) -> Self { + match value { + AbstractTypeDefinitionPosition::Interface(value) => value.into(), + AbstractTypeDefinitionPosition::Union(value) => value.into(), + } + } +} + +impl From for CompositeTypeDefinitionPosition { + fn from(value: ObjectOrInterfaceTypeDefinitionPosition) -> Self { + match value { + ObjectOrInterfaceTypeDefinitionPosition::Object(value) => value.into(), + ObjectOrInterfaceTypeDefinitionPosition::Interface(value) => value.into(), + } + } +} + +impl TryFrom for CompositeTypeDefinitionPosition { + type Error = FederationError; + + fn try_from(value: QueryGraphNodeType) -> Result { + match value { + QueryGraphNodeType::SchemaType(ty) => ty.try_into(), + QueryGraphNodeType::FederatedRootType(_) => Err(FederationError::internal(format!( + "Type `{value}` was unexpectedly not a composite type" + ))), + } + } +} + +impl TryFrom for ObjectTypeDefinitionPosition { + type Error = FederationError; + + fn try_from(value: QueryGraphNodeType) -> Result { + match value { + QueryGraphNodeType::SchemaType(ty) => ty.try_into(), + QueryGraphNodeType::FederatedRootType(_) => Err(FederationError::internal(format!( + "Type `{value}` was unexpectedly not a composite type" + ))), + } + } +} + +impl TryFrom for ObjectTypeDefinitionPosition { + type Error = FederationError; + + fn try_from(value: CompositeTypeDefinitionPosition) -> Result { + match value { + CompositeTypeDefinitionPosition::Object(value) => Ok(value), + _ => Err(FederationError::internal(format!( + "Type `{value}` was unexpectedly not an object type" + ))), + } + } +} + +#[derive(Clone, PartialEq, Eq, Hash, derive_more::From, derive_more::Display)] +pub(crate) enum AbstractTypeDefinitionPosition { + Interface(InterfaceTypeDefinitionPosition), + Union(UnionTypeDefinitionPosition), +} + +impl Debug for AbstractTypeDefinitionPosition { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::Interface(p) => write!(f, "Interface({p})"), + Self::Union(p) => write!(f, "Union({p})"), + } + } +} + +impl AbstractTypeDefinitionPosition { + pub(crate) fn type_name(&self) -> &Name { + match self { + AbstractTypeDefinitionPosition::Interface(type_) => &type_.type_name, + AbstractTypeDefinitionPosition::Union(type_) => &type_.type_name, + } + } +} + +impl TryFrom for AbstractTypeDefinitionPosition { + type Error = FederationError; + + fn try_from(value: TypeDefinitionPosition) -> Result { + match value { + TypeDefinitionPosition::Interface(value) => { + Ok(AbstractTypeDefinitionPosition::Interface(value)) + } + TypeDefinitionPosition::Union(value) => { + Ok(AbstractTypeDefinitionPosition::Union(value)) + } + _ => Err(FederationError::internal(format!( + "Type \"{}\" was unexpectedly not an interface/union type", + value, + ))), + } + } +} + +impl TryFrom for AbstractTypeDefinitionPosition { + type Error = FederationError; + + fn try_from(value: OutputTypeDefinitionPosition) -> Result { + match value { + OutputTypeDefinitionPosition::Interface(value) => { + Ok(AbstractTypeDefinitionPosition::Interface(value)) + } + OutputTypeDefinitionPosition::Union(value) => { + Ok(AbstractTypeDefinitionPosition::Union(value)) + } + _ => Err(FederationError::internal(format!( + "Type \"{}\" was unexpectedly not an interface/union type", + value, + ))), + } + } +} + +#[derive(Clone, PartialEq, Eq, Hash, derive_more::From, derive_more::Display)] +pub(crate) enum ObjectOrInterfaceTypeDefinitionPosition { + Object(ObjectTypeDefinitionPosition), + Interface(InterfaceTypeDefinitionPosition), +} + +impl Debug for ObjectOrInterfaceTypeDefinitionPosition { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::Object(p) => write!(f, "Object({p})"), + Self::Interface(p) => write!(f, "Interface({p})"), + } + } +} + +impl ObjectOrInterfaceTypeDefinitionPosition { + pub(crate) fn type_name(&self) -> &Name { + match self { + ObjectOrInterfaceTypeDefinitionPosition::Object(type_) => &type_.type_name, + ObjectOrInterfaceTypeDefinitionPosition::Interface(type_) => &type_.type_name, + } + } + + pub(crate) fn field(&self, field_name: Name) -> ObjectOrInterfaceFieldDefinitionPosition { + match self { + ObjectOrInterfaceTypeDefinitionPosition::Object(type_) => { + type_.field(field_name).into() + } + ObjectOrInterfaceTypeDefinitionPosition::Interface(type_) => { + type_.field(field_name).into() + } + } + } +} + +impl TryFrom for ObjectOrInterfaceTypeDefinitionPosition { + type Error = FederationError; + + fn try_from(value: TypeDefinitionPosition) -> Result { + match value { + TypeDefinitionPosition::Object(value) => { + Ok(ObjectOrInterfaceTypeDefinitionPosition::Object(value)) + } + TypeDefinitionPosition::Interface(value) => { + Ok(ObjectOrInterfaceTypeDefinitionPosition::Interface(value)) + } + _ => Err(SingleFederationError::Internal { + message: format!( + "Type \"{}\" was unexpectedly not an object/interface type", + value, + ), + } + .into()), + } + } +} + +impl TryFrom for ObjectOrInterfaceTypeDefinitionPosition { + type Error = FederationError; + + fn try_from(value: OutputTypeDefinitionPosition) -> Result { + match value { + OutputTypeDefinitionPosition::Object(value) => { + Ok(ObjectOrInterfaceTypeDefinitionPosition::Object(value)) + } + OutputTypeDefinitionPosition::Interface(value) => { + Ok(ObjectOrInterfaceTypeDefinitionPosition::Interface(value)) + } + _ => Err(SingleFederationError::Internal { + message: format!( + "Output type \"{}\" was unexpectedly not an object/interface type", + value, + ), + } + .into()), + } + } +} + +#[derive(Clone, PartialEq, Eq, Hash, derive_more::From, derive_more::Display)] +pub(crate) enum FieldDefinitionPosition { + Object(ObjectFieldDefinitionPosition), + Interface(InterfaceFieldDefinitionPosition), + Union(UnionTypenameFieldDefinitionPosition), +} + +impl Debug for FieldDefinitionPosition { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::Object(p) => write!(f, "Object({p})"), + Self::Interface(p) => write!(f, "Interface({p})"), + Self::Union(p) => write!(f, "Union({p})"), + } + } +} + +impl FieldDefinitionPosition { + pub(crate) fn type_name(&self) -> &Name { + match self { + FieldDefinitionPosition::Object(field) => &field.type_name, + FieldDefinitionPosition::Interface(field) => &field.type_name, + FieldDefinitionPosition::Union(field) => &field.type_name, + } + } + + pub(crate) fn field_name(&self) -> &Name { + match self { + FieldDefinitionPosition::Object(field) => &field.field_name, + FieldDefinitionPosition::Interface(field) => &field.field_name, + FieldDefinitionPosition::Union(field) => field.field_name(), + } + } + + pub(crate) fn is_introspection_typename_field(&self) -> bool { + *self.field_name() == *INTROSPECTION_TYPENAME_FIELD_NAME + } + + pub(crate) fn parent(&self) -> CompositeTypeDefinitionPosition { + match self { + FieldDefinitionPosition::Object(field) => field.parent().into(), + FieldDefinitionPosition::Interface(field) => field.parent().into(), + FieldDefinitionPosition::Union(field) => field.parent().into(), + } + } + + pub(crate) fn get<'schema>( + &self, + schema: &'schema Schema, + ) -> Result<&'schema Component, FederationError> { + match self { + FieldDefinitionPosition::Object(field) => field.get(schema), + FieldDefinitionPosition::Interface(field) => field.get(schema), + FieldDefinitionPosition::Union(field) => field.get(schema), + } + } +} + +impl From for FieldDefinitionPosition { + fn from(value: ObjectOrInterfaceFieldDefinitionPosition) -> Self { + match value { + ObjectOrInterfaceFieldDefinitionPosition::Object(value) => value.into(), + ObjectOrInterfaceFieldDefinitionPosition::Interface(value) => value.into(), + } + } +} + +#[derive(Clone, PartialEq, Eq, Hash, derive_more::From, derive_more::Display)] +pub(crate) enum ObjectOrInterfaceFieldDefinitionPosition { + Object(ObjectFieldDefinitionPosition), + Interface(InterfaceFieldDefinitionPosition), +} + +impl Debug for ObjectOrInterfaceFieldDefinitionPosition { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::Object(p) => write!(f, "Object({p})"), + Self::Interface(p) => write!(f, "Interface({p})"), + } + } +} + +impl ObjectOrInterfaceFieldDefinitionPosition { + pub(crate) fn type_name(&self) -> &Name { + match self { + ObjectOrInterfaceFieldDefinitionPosition::Object(field) => &field.type_name, + ObjectOrInterfaceFieldDefinitionPosition::Interface(field) => &field.type_name, + } + } + + pub(crate) fn field_name(&self) -> &Name { + match self { + ObjectOrInterfaceFieldDefinitionPosition::Object(field) => &field.field_name, + ObjectOrInterfaceFieldDefinitionPosition::Interface(field) => &field.field_name, + } + } + + pub(crate) fn is_introspection_typename_field(&self) -> bool { + *self.field_name() == *INTROSPECTION_TYPENAME_FIELD_NAME + } + + pub(crate) fn parent(&self) -> ObjectOrInterfaceTypeDefinitionPosition { + match self { + ObjectOrInterfaceFieldDefinitionPosition::Object(field) => field.parent().into(), + ObjectOrInterfaceFieldDefinitionPosition::Interface(field) => field.parent().into(), + } + } + + pub(crate) fn get<'schema>( + &self, + schema: &'schema Schema, + ) -> Result<&'schema Component, FederationError> { + match self { + ObjectOrInterfaceFieldDefinitionPosition::Object(field) => field.get(schema), + ObjectOrInterfaceFieldDefinitionPosition::Interface(field) => field.get(schema), + } + } + + pub(crate) fn insert_directive( + &self, + schema: &mut FederationSchema, + directive: Node, + ) -> Result<(), FederationError> { + match self { + ObjectOrInterfaceFieldDefinitionPosition::Object(field) => { + field.insert_directive(schema, directive) + } + ObjectOrInterfaceFieldDefinitionPosition::Interface(field) => { + field.insert_directive(schema, directive) + } + } + } + + pub(crate) fn remove_directive( + &self, + schema: &mut FederationSchema, + directive: &Node, + ) { + match self { + ObjectOrInterfaceFieldDefinitionPosition::Object(field) => { + field.remove_directive(schema, directive) + } + ObjectOrInterfaceFieldDefinitionPosition::Interface(field) => { + field.remove_directive(schema, directive) + } + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub(crate) struct SchemaDefinitionPosition; + +impl SchemaDefinitionPosition { + pub(crate) fn get<'schema>(&self, schema: &'schema Schema) -> &'schema Node { + &schema.schema_definition + } + + fn make_mut<'schema>( + &self, + schema: &'schema mut Schema, + ) -> &'schema mut Node { + &mut schema.schema_definition + } + + pub(crate) fn insert_directive( + &self, + schema: &mut FederationSchema, + directive: Component, + ) -> Result<(), FederationError> { + let schema_definition = self.make_mut(&mut schema.schema); + if schema_definition + .directives + .iter() + .any(|other_directive| other_directive.ptr_eq(&directive)) + { + return Err(SingleFederationError::Internal { + message: format!( + "Directive application \"@{}\" already exists on schema definition", + directive.name, + ), + } + .into()); + } + let name = directive.name.clone(); + schema_definition.make_mut().directives.push(directive); + self.insert_directive_name_references(&mut schema.referencers, &name)?; + schema.links_metadata = links_metadata(&schema.schema)?.map(Box::new); + Ok(()) + } + + pub(crate) fn remove_directive_name( + &self, + schema: &mut FederationSchema, + name: &str, + ) -> Result<(), FederationError> { + let is_link = Self::is_link(schema, name)?; + self.remove_directive_name_references(&mut schema.referencers, name); + self.make_mut(&mut schema.schema) + .make_mut() + .directives + .retain(|other_directive| other_directive.name != name); + if is_link { + schema.links_metadata = links_metadata(&schema.schema)?.map(Box::new); + } + Ok(()) + } + + pub(crate) fn remove_directive( + &self, + schema: &mut FederationSchema, + directive: &Component, + ) -> Result<(), FederationError> { + let is_link = Self::is_link(schema, &directive.name)?; + let schema_definition = self.make_mut(&mut schema.schema); + if !schema_definition.directives.iter().any(|other_directive| { + (other_directive.name == directive.name) && !other_directive.ptr_eq(directive) + }) { + self.remove_directive_name_references(&mut schema.referencers, &directive.name); + } + schema_definition + .make_mut() + .directives + .retain(|other_directive| !other_directive.ptr_eq(directive)); + if is_link { + schema.links_metadata = links_metadata(&schema.schema)?.map(Box::new); + } + Ok(()) + } + + fn insert_references( + &self, + schema_definition: &Node, + schema: &Schema, + referencers: &mut Referencers, + ) -> Result<(), FederationError> { + validate_component_directives(schema_definition.directives.deref())?; + for directive_reference in schema_definition.directives.iter() { + self.insert_directive_name_references(referencers, &directive_reference.name)?; + } + for root_kind in SchemaRootDefinitionKind::iter() { + let child = SchemaRootDefinitionPosition { root_kind }; + match root_kind { + SchemaRootDefinitionKind::Query => { + if let Some(root_type) = &schema_definition.query { + child.insert_references(root_type, schema, referencers)?; + } + } + SchemaRootDefinitionKind::Mutation => { + if let Some(root_type) = &schema_definition.mutation { + child.insert_references(root_type, schema, referencers)?; + } + } + SchemaRootDefinitionKind::Subscription => { + if let Some(root_type) = &schema_definition.subscription { + child.insert_references(root_type, schema, referencers)?; + } + } + } + } + Ok(()) + } + + fn insert_directive_name_references( + &self, + referencers: &mut Referencers, + name: &Name, + ) -> Result<(), FederationError> { + let directive_referencers = referencers.directives.get_mut(name).ok_or_else(|| { + SingleFederationError::Internal { + message: format!( + "Schema definition's directive application \"@{}\" does not refer to an existing directive.", + name, + ), + } + })?; + directive_referencers.schema = Some(SchemaDefinitionPosition); + Ok(()) + } + + fn remove_directive_name_references(&self, referencers: &mut Referencers, name: &str) { + let Some(directive_referencers) = referencers.directives.get_mut(name) else { + return; + }; + directive_referencers.schema = None; + } + + fn is_link(schema: &FederationSchema, name: &str) -> Result { + Ok(match schema.metadata() { + Some(metadata) => { + let link_spec_definition = metadata.link_spec_definition()?; + let link_name_in_schema = link_spec_definition + .directive_name_in_schema(schema, &link_spec_definition.identity().name)? + .ok_or_else(|| SingleFederationError::Internal { + message: "Unexpectedly could not find core/link spec usage".to_owned(), + })?; + link_name_in_schema == name + } + None => false, + }) + } +} + +#[derive( + Debug, Copy, Clone, PartialEq, Eq, Hash, strum_macros::Display, strum_macros::EnumIter, +)] +pub(crate) enum SchemaRootDefinitionKind { + #[strum(to_string = "query")] + Query, + #[strum(to_string = "mutation")] + Mutation, + #[strum(to_string = "subscription")] + Subscription, +} + +impl From for ast::OperationType { + fn from(value: SchemaRootDefinitionKind) -> Self { + match value { + SchemaRootDefinitionKind::Query => ast::OperationType::Query, + SchemaRootDefinitionKind::Mutation => ast::OperationType::Mutation, + SchemaRootDefinitionKind::Subscription => ast::OperationType::Subscription, + } + } +} + +impl From for SchemaRootDefinitionKind { + fn from(value: ast::OperationType) -> Self { + match value { + ast::OperationType::Query => SchemaRootDefinitionKind::Query, + ast::OperationType::Mutation => SchemaRootDefinitionKind::Mutation, + ast::OperationType::Subscription => SchemaRootDefinitionKind::Subscription, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub(crate) struct SchemaRootDefinitionPosition { + pub(crate) root_kind: SchemaRootDefinitionKind, +} + +impl SchemaRootDefinitionPosition { + pub(crate) fn parent(&self) -> SchemaDefinitionPosition { + SchemaDefinitionPosition + } + + pub(crate) fn get<'schema>( + &self, + schema: &'schema Schema, + ) -> Result<&'schema ComponentName, FederationError> { + let schema_definition = self.parent().get(schema); + + match self.root_kind { + SchemaRootDefinitionKind::Query => schema_definition.query.as_ref().ok_or_else(|| { + SingleFederationError::Internal { + message: format!("Schema definition has no root {} type", self), + } + .into() + }), + SchemaRootDefinitionKind::Mutation => { + schema_definition.mutation.as_ref().ok_or_else(|| { + SingleFederationError::Internal { + message: format!("Schema definition has no root {} type", self), + } + .into() + }) + } + SchemaRootDefinitionKind::Subscription => { + schema_definition.subscription.as_ref().ok_or_else(|| { + SingleFederationError::Internal { + message: format!("Schema definition has no root {} type", self), + } + .into() + }) + } + } + } + + pub(crate) fn try_get<'schema>( + &self, + schema: &'schema Schema, + ) -> Option<&'schema ComponentName> { + self.get(schema).ok() + } + + fn make_mut<'schema>( + &self, + schema: &'schema mut Schema, + ) -> Result<&'schema mut ComponentName, FederationError> { + let schema_definition = self.parent().make_mut(schema).make_mut(); + + match self.root_kind { + SchemaRootDefinitionKind::Query => schema_definition.query.as_mut().ok_or_else(|| { + SingleFederationError::Internal { + message: format!("Schema definition has no root {} type", self), + } + .into() + }), + SchemaRootDefinitionKind::Mutation => { + schema_definition.mutation.as_mut().ok_or_else(|| { + SingleFederationError::Internal { + message: format!("Schema definition has no root {} type", self), + } + .into() + }) + } + SchemaRootDefinitionKind::Subscription => { + schema_definition.subscription.as_mut().ok_or_else(|| { + SingleFederationError::Internal { + message: format!("Schema definition has no root {} type", self), + } + .into() + }) + } + } + } + + fn try_make_mut<'schema>( + &self, + schema: &'schema mut Schema, + ) -> Option<&'schema mut ComponentName> { + if self.try_get(schema).is_some() { + self.make_mut(schema).ok() + } else { + None + } + } + + pub(crate) fn insert( + &self, + schema: &mut FederationSchema, + root_type: ComponentName, + ) -> Result<(), FederationError> { + if self.try_get(&schema.schema).is_some() { + return Err(SingleFederationError::Internal { + message: format!("Root {} already exists on schema definition", self), + } + .into()); + } + let parent = self.parent().make_mut(&mut schema.schema).make_mut(); + match self.root_kind { + SchemaRootDefinitionKind::Query => { + parent.query = Some(root_type); + } + SchemaRootDefinitionKind::Mutation => { + parent.mutation = Some(root_type); + } + SchemaRootDefinitionKind::Subscription => { + parent.subscription = Some(root_type); + } + } + self.insert_references( + self.get(&schema.schema)?, + &schema.schema, + &mut schema.referencers, + ) + } + + pub(crate) fn remove(&self, schema: &mut FederationSchema) -> Result<(), FederationError> { + let Some(root_type) = self.try_get(&schema.schema) else { + return Ok(()); + }; + self.remove_references(root_type, &schema.schema, &mut schema.referencers)?; + let parent = self.parent().make_mut(&mut schema.schema).make_mut(); + match self.root_kind { + SchemaRootDefinitionKind::Query => { + parent.query = None; + } + SchemaRootDefinitionKind::Mutation => { + parent.mutation = None; + } + SchemaRootDefinitionKind::Subscription => { + parent.subscription = None; + } + } + Ok(()) + } + + fn insert_references( + &self, + root_type: &ComponentName, + schema: &Schema, + referencers: &mut Referencers, + ) -> Result<(), FederationError> { + let object_type_referencers = referencers + .object_types + .get_mut(root_type.deref()) + .ok_or_else(|| SingleFederationError::Internal { + message: format!( + "Root {} type \"{}\" does not refer to an existing object type.", + self, + root_type.deref() + ), + })?; + object_type_referencers.schema_roots.insert(self.clone()); + if self.root_kind == SchemaRootDefinitionKind::Query { + ObjectTypeDefinitionPosition { + type_name: root_type.name.clone(), + } + .insert_root_query_references(schema, referencers)?; + } + Ok(()) + } + + fn remove_references( + &self, + root_type: &ComponentName, + schema: &Schema, + referencers: &mut Referencers, + ) -> Result<(), FederationError> { + if self.root_kind == SchemaRootDefinitionKind::Query { + ObjectTypeDefinitionPosition { + type_name: root_type.name.clone(), + } + .remove_root_query_references(schema, referencers)?; + } + let Some(object_type_referencers) = referencers.object_types.get_mut(root_type.deref()) + else { + return Ok(()); + }; + object_type_referencers.schema_roots.shift_remove(self); + Ok(()) + } +} + +impl Display for SchemaRootDefinitionPosition { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.root_kind) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub(crate) struct ScalarTypeDefinitionPosition { + pub(crate) type_name: Name, +} + +impl ScalarTypeDefinitionPosition { + pub(crate) fn get<'schema>( + &self, + schema: &'schema Schema, + ) -> Result<&'schema Node, FederationError> { + schema + .types + .get(&self.type_name) + .ok_or_else(|| { + SingleFederationError::Internal { + message: format!("Schema has no type \"{}\"", self), + } + .into() + }) + .and_then(|type_| { + if let ExtendedType::Scalar(type_) = type_ { + Ok(type_) + } else { + Err(SingleFederationError::Internal { + message: format!("Schema type \"{}\" was not a scalar", self), + } + .into()) + } + }) + } + + pub(crate) fn try_get<'schema>( + &self, + schema: &'schema Schema, + ) -> Option<&'schema Node> { + self.get(schema).ok() + } + + fn make_mut<'schema>( + &self, + schema: &'schema mut Schema, + ) -> Result<&'schema mut Node, FederationError> { + schema + .types + .get_mut(&self.type_name) + .ok_or_else(|| { + SingleFederationError::Internal { + message: format!("Schema has no type \"{}\"", self), + } + .into() + }) + .and_then(|type_| { + if let ExtendedType::Scalar(type_) = type_ { + Ok(type_) + } else { + Err(SingleFederationError::Internal { + message: format!("Schema type \"{}\" was not a scalar", self), + } + .into()) + } + }) + } + + fn try_make_mut<'schema>( + &self, + schema: &'schema mut Schema, + ) -> Option<&'schema mut Node> { + if self.try_get(schema).is_some() { + self.make_mut(schema).ok() + } else { + None + } + } + + pub(crate) fn pre_insert(&self, schema: &mut FederationSchema) -> Result<(), FederationError> { + if schema.referencers.contains_type_name(&self.type_name) { + // TODO: Allow built-in shadowing instead of ignoring them + if is_graphql_reserved_name(&self.type_name) + || GRAPHQL_BUILTIN_SCALAR_NAMES.contains(&self.type_name) + { + return Ok(()); + } + return Err(SingleFederationError::Internal { + message: format!("Type \"{}\" has already been pre-inserted", self), + } + .into()); + } + schema + .referencers + .scalar_types + .insert(self.type_name.clone(), Default::default()); + Ok(()) + } + + pub(crate) fn insert( + &self, + schema: &mut FederationSchema, + type_: Node, + ) -> Result<(), FederationError> { + if self.type_name != type_.name { + return Err(SingleFederationError::Internal { + message: format!( + "Scalar type \"{}\" given type named \"{}\"", + self, type_.name, + ), + } + .into()); + } + if !schema + .referencers + .scalar_types + .contains_key(&self.type_name) + { + return Err(SingleFederationError::Internal { + message: format!("Type \"{}\" has not been pre-inserted", self), + } + .into()); + } + if schema.schema.types.contains_key(&self.type_name) { + // TODO: Allow built-in shadowing instead of ignoring them + if is_graphql_reserved_name(&self.type_name) + || GRAPHQL_BUILTIN_SCALAR_NAMES.contains(&self.type_name) + { + return Ok(()); + } + return Err(SingleFederationError::Internal { + message: format!("Type \"{}\" already exists in schema", self), + } + .into()); + } + schema + .schema + .types + .insert(self.type_name.clone(), ExtendedType::Scalar(type_)); + self.insert_references(self.get(&schema.schema)?, &mut schema.referencers) + } + + pub(crate) fn remove( + &self, + schema: &mut FederationSchema, + ) -> Result, FederationError> { + let Some(referencers) = self.remove_internal(schema)? else { + return Ok(None); + }; + for field in &referencers.object_fields { + field.remove(schema)?; + } + for argument in &referencers.object_field_arguments { + argument.remove(schema)?; + } + for field in &referencers.interface_fields { + field.remove(schema)?; + } + for argument in &referencers.interface_field_arguments { + argument.remove(schema)?; + } + for field in &referencers.input_object_fields { + field.remove(schema)?; + } + for argument in &referencers.directive_arguments { + argument.remove(schema)?; + } + Ok(Some(referencers)) + } + + pub(crate) fn remove_recursive( + &self, + schema: &mut FederationSchema, + ) -> Result<(), FederationError> { + let Some(referencers) = self.remove_internal(schema)? else { + return Ok(()); + }; + for field in referencers.object_fields { + field.remove_recursive(schema)?; + } + for argument in referencers.object_field_arguments { + argument.remove(schema)?; + } + for field in referencers.interface_fields { + field.remove_recursive(schema)?; + } + for argument in referencers.interface_field_arguments { + argument.remove(schema)?; + } + for field in referencers.input_object_fields { + field.remove_recursive(schema)?; + } + for argument in referencers.directive_arguments { + argument.remove(schema)?; + } + Ok(()) + } + + fn remove_internal( + &self, + schema: &mut FederationSchema, + ) -> Result, FederationError> { + let Some(type_) = self.try_get(&schema.schema) else { + return Ok(None); + }; + self.remove_references(type_, &mut schema.referencers); + schema.schema.types.shift_remove(&self.type_name); + Ok(Some( + schema + .referencers + .scalar_types + .shift_remove(&self.type_name) + .ok_or_else(|| SingleFederationError::Internal { + message: format!("Schema missing referencers for type \"{}\"", self), + })?, + )) + } + + pub(crate) fn insert_directive( + &self, + schema: &mut FederationSchema, + directive: Component, + ) -> Result<(), FederationError> { + let type_ = self.make_mut(&mut schema.schema)?; + if type_ + .directives + .iter() + .any(|other_directive| other_directive.ptr_eq(&directive)) + { + return Err(SingleFederationError::Internal { + message: format!( + "Directive application \"@{}\" already exists on scalar type \"{}\"", + directive.name, self, + ), + } + .into()); + } + let name = directive.name.clone(); + type_.make_mut().directives.push(directive); + self.insert_directive_name_references(&mut schema.referencers, &name) + } + + pub(crate) fn remove_directive_name(&self, schema: &mut FederationSchema, name: &str) { + let Some(type_) = self.try_make_mut(&mut schema.schema) else { + return; + }; + self.remove_directive_name_references(&mut schema.referencers, name); + type_ + .make_mut() + .directives + .retain(|other_directive| other_directive.name != name); + } + + pub(crate) fn remove_directive( + &self, + schema: &mut FederationSchema, + directive: &Component, + ) { + let Some(type_) = self.try_make_mut(&mut schema.schema) else { + return; + }; + if !type_.directives.iter().any(|other_directive| { + (other_directive.name == directive.name) && !other_directive.ptr_eq(directive) + }) { + self.remove_directive_name_references(&mut schema.referencers, &directive.name); + } + type_ + .make_mut() + .directives + .retain(|other_directive| !other_directive.ptr_eq(directive)); + } + + fn insert_references( + &self, + type_: &Node, + referencers: &mut Referencers, + ) -> Result<(), FederationError> { + validate_component_directives(type_.directives.deref())?; + for directive_reference in type_.directives.iter() { + self.insert_directive_name_references(referencers, &directive_reference.name)?; + } + Ok(()) + } + + fn remove_references(&self, type_: &Node, referencers: &mut Referencers) { + for directive_reference in type_.directives.iter() { + self.remove_directive_name_references(referencers, &directive_reference.name); + } + } + + fn insert_directive_name_references( + &self, + referencers: &mut Referencers, + name: &Name, + ) -> Result<(), FederationError> { + let directive_referencers = referencers.directives.get_mut(name).ok_or_else(|| { + SingleFederationError::Internal { + message: format!( + "Scalar type \"{}\"'s directive application \"@{}\" does not refer to an existing directive.", + self, + name, + ), + } + })?; + directive_referencers.scalar_types.insert(self.clone()); + Ok(()) + } + + fn remove_directive_name_references(&self, referencers: &mut Referencers, name: &str) { + let Some(directive_referencers) = referencers.directives.get_mut(name) else { + return; + }; + directive_referencers.scalar_types.shift_remove(self); + } +} + +impl Display for ScalarTypeDefinitionPosition { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.type_name) + } +} + +#[derive(Clone, PartialEq, Eq, Hash)] +pub(crate) struct ObjectTypeDefinitionPosition { + pub(crate) type_name: Name, +} + +impl ObjectTypeDefinitionPosition { + pub(crate) fn new(type_name: Name) -> Self { + Self { type_name } + } + + pub(crate) fn field(&self, field_name: Name) -> ObjectFieldDefinitionPosition { + ObjectFieldDefinitionPosition { + type_name: self.type_name.clone(), + field_name, + } + } + + pub(crate) fn introspection_typename_field(&self) -> ObjectFieldDefinitionPosition { + self.field(INTROSPECTION_TYPENAME_FIELD_NAME.clone()) + } + + pub(crate) fn introspection_schema_field(&self) -> ObjectFieldDefinitionPosition { + self.field(name!("__schema")) + } + + pub(crate) fn introspection_type_field(&self) -> ObjectFieldDefinitionPosition { + self.field(name!("__type")) + } + + pub(crate) fn get<'schema>( + &self, + schema: &'schema Schema, + ) -> Result<&'schema Node, FederationError> { + schema + .types + .get(&self.type_name) + .ok_or_else(|| { + SingleFederationError::Internal { + message: format!("Schema has no type \"{}\"", self), + } + .into() + }) + .and_then(|type_| { + if let ExtendedType::Object(type_) = type_ { + Ok(type_) + } else { + Err(SingleFederationError::Internal { + message: format!("Schema type \"{}\" was not an object", self), + } + .into()) + } + }) + } + + pub(crate) fn try_get<'schema>( + &self, + schema: &'schema Schema, + ) -> Option<&'schema Node> { + self.get(schema).ok() + } + + fn make_mut<'schema>( + &self, + schema: &'schema mut Schema, + ) -> Result<&'schema mut Node, FederationError> { + schema + .types + .get_mut(&self.type_name) + .ok_or_else(|| { + SingleFederationError::Internal { + message: format!("Schema has no type \"{}\"", self), + } + .into() + }) + .and_then(|type_| { + if let ExtendedType::Object(type_) = type_ { + Ok(type_) + } else { + Err(SingleFederationError::Internal { + message: format!("Schema type \"{}\" was not an object", self), + } + .into()) + } + }) + } + + fn try_make_mut<'schema>( + &self, + schema: &'schema mut Schema, + ) -> Option<&'schema mut Node> { + if self.try_get(schema).is_some() { + self.make_mut(schema).ok() + } else { + None + } + } + + pub(crate) fn pre_insert(&self, schema: &mut FederationSchema) -> Result<(), FederationError> { + if schema.referencers.contains_type_name(&self.type_name) { + // TODO: Allow built-in shadowing instead of ignoring them + if is_graphql_reserved_name(&self.type_name) { + return Ok(()); + } + return Err(SingleFederationError::Internal { + message: format!("Type \"{}\" has already been pre-inserted", self), + } + .into()); + } + schema + .referencers + .object_types + .insert(self.type_name.clone(), Default::default()); + Ok(()) + } + + pub(crate) fn insert( + &self, + schema: &mut FederationSchema, + type_: Node, + ) -> Result<(), FederationError> { + if self.type_name != type_.name { + return Err(SingleFederationError::Internal { + message: format!( + "Object type \"{}\" given type named \"{}\"", + self, type_.name, + ), + } + .into()); + } + if !schema + .referencers + .object_types + .contains_key(&self.type_name) + { + return Err(SingleFederationError::Internal { + message: format!("Type \"{}\" has not been pre-inserted", self), + } + .into()); + } + if schema.schema.types.contains_key(&self.type_name) { + // TODO: Allow built-in shadowing instead of ignoring them + if is_graphql_reserved_name(&self.type_name) { + return Ok(()); + } + return Err(SingleFederationError::Internal { + message: format!("Type \"{}\" already exists in schema", self), + } + .into()); + } + schema + .schema + .types + .insert(self.type_name.clone(), ExtendedType::Object(type_)); + self.insert_references( + self.get(&schema.schema)?, + &schema.schema, + &mut schema.referencers, + ) + } + + pub(crate) fn remove( + &self, + schema: &mut FederationSchema, + ) -> Result, FederationError> { + let Some(referencers) = self.remove_internal(schema)? else { + return Ok(None); + }; + for root in &referencers.schema_roots { + root.remove(schema)?; + } + for field in &referencers.object_fields { + field.remove(schema)?; + } + for field in &referencers.interface_fields { + field.remove(schema)?; + } + for type_ in &referencers.union_types { + type_.remove_member(schema, &self.type_name); + } + Ok(Some(referencers)) + } + + pub(crate) fn remove_recursive( + &self, + schema: &mut FederationSchema, + ) -> Result<(), FederationError> { + let Some(referencers) = self.remove_internal(schema)? else { + return Ok(()); + }; + for root in referencers.schema_roots { + root.remove(schema)?; + } + for field in referencers.object_fields { + field.remove_recursive(schema)?; + } + for field in referencers.interface_fields { + field.remove_recursive(schema)?; + } + for type_ in referencers.union_types { + type_.remove_member_recursive(schema, &self.type_name)?; + } + Ok(()) + } + + fn remove_internal( + &self, + schema: &mut FederationSchema, + ) -> Result, FederationError> { + let Some(type_) = self.try_get(&schema.schema) else { + return Ok(None); + }; + self.remove_references(type_, &schema.schema, &mut schema.referencers)?; + schema.schema.types.shift_remove(&self.type_name); + Ok(Some( + schema + .referencers + .object_types + .shift_remove(&self.type_name) + .ok_or_else(|| SingleFederationError::Internal { + message: format!("Schema missing referencers for type \"{}\"", self), + })?, + )) + } + + pub(crate) fn insert_directive( + &self, + schema: &mut FederationSchema, + directive: Component, + ) -> Result<(), FederationError> { + let type_ = self.make_mut(&mut schema.schema)?; + if type_ + .directives + .iter() + .any(|other_directive| other_directive.ptr_eq(&directive)) + { + return Err(SingleFederationError::Internal { + message: format!( + "Directive application \"@{}\" already exists on object type \"{}\"", + directive.name, self, + ), + } + .into()); + } + let name = directive.name.clone(); + type_.make_mut().directives.push(directive); + self.insert_directive_name_references(&mut schema.referencers, &name) + } + + pub(crate) fn remove_directive_name(&self, schema: &mut FederationSchema, name: &str) { + let Some(type_) = self.try_make_mut(&mut schema.schema) else { + return; + }; + self.remove_directive_name_references(&mut schema.referencers, name); + type_ + .make_mut() + .directives + .retain(|other_directive| other_directive.name != name); + } + + pub(crate) fn remove_directive( + &self, + schema: &mut FederationSchema, + directive: &Component, + ) { + let Some(type_) = self.try_make_mut(&mut schema.schema) else { + return; + }; + if !type_.directives.iter().any(|other_directive| { + (other_directive.name == directive.name) && !other_directive.ptr_eq(directive) + }) { + self.remove_directive_name_references(&mut schema.referencers, &directive.name); + } + type_ + .make_mut() + .directives + .retain(|other_directive| !other_directive.ptr_eq(directive)); + } + + pub(crate) fn insert_implements_interface( + &self, + schema: &mut FederationSchema, + name: ComponentName, + ) -> Result<(), FederationError> { + let type_ = self.make_mut(&mut schema.schema)?; + type_.make_mut().implements_interfaces.insert(name.clone()); + self.insert_implements_interface_references(&mut schema.referencers, &name) + } + + pub(crate) fn remove_implements_interface(&self, schema: &mut FederationSchema, name: &str) { + let Some(type_) = self.try_make_mut(&mut schema.schema) else { + return; + }; + self.remove_implements_interface_references(&mut schema.referencers, name); + type_ + .make_mut() + .implements_interfaces + .retain(|other_type| other_type != name); + } + + fn insert_references( + &self, + type_: &Node, + schema: &Schema, + referencers: &mut Referencers, + ) -> Result<(), FederationError> { + validate_component_directives(type_.directives.deref())?; + for directive_reference in type_.directives.iter() { + self.insert_directive_name_references(referencers, &directive_reference.name)?; + } + for interface_type_reference in type_.implements_interfaces.iter() { + self.insert_implements_interface_references( + referencers, + interface_type_reference.deref(), + )?; + } + let introspection_typename_field = self.introspection_typename_field(); + introspection_typename_field.insert_references( + introspection_typename_field.get(schema)?, + schema, + referencers, + true, + )?; + if let Some(root_query_type) = (SchemaRootDefinitionPosition { + root_kind: SchemaRootDefinitionKind::Query, + }) + .try_get(schema) + { + // Note that when inserting an object type that's the root query type, it's possible for + // the root query type to have been set before this insertion. During that set, while + // we would call insert_root_query_references(), it would ultimately do nothing since + // the meta-fields wouldn't be found (since the type has only been pre-inserted at that + // point, not fully inserted). We instead need to execute the reference insertion here, + // as it's right after the type has been inserted. + if self.type_name == root_query_type.name { + self.insert_root_query_references(schema, referencers)?; + } + } + for (field_name, field) in type_.fields.iter() { + self.field(field_name.clone()) + .insert_references(field, schema, referencers, false)?; + } + Ok(()) + } + + fn remove_references( + &self, + type_: &Node, + schema: &Schema, + referencers: &mut Referencers, + ) -> Result<(), FederationError> { + for directive_reference in type_.directives.iter() { + self.remove_directive_name_references(referencers, &directive_reference.name); + } + for interface_type_reference in type_.implements_interfaces.iter() { + self.remove_implements_interface_references( + referencers, + interface_type_reference.deref(), + ); + } + let introspection_typename_field = self.introspection_typename_field(); + introspection_typename_field.remove_references( + introspection_typename_field.get(schema)?, + schema, + referencers, + true, + )?; + if let Some(root_query_type) = (SchemaRootDefinitionPosition { + root_kind: SchemaRootDefinitionKind::Query, + }) + .try_get(schema) + { + // Note that when removing an object type that's the root query type, it will eventually + // call SchemaRootDefinitionPosition.remove() to unset the root query type, and there's + // code there to call remove_root_query_references(). However, that code won't find the + // meta-fields __schema or __type, as the type has already been removed from the schema + // before it executes. We instead need to execute the reference removal here, as it's + // right before the type has been removed. + if self.type_name == root_query_type.name { + self.remove_root_query_references(schema, referencers)?; + } + } + for (field_name, field) in type_.fields.iter() { + self.field(field_name.clone()) + .remove_references(field, schema, referencers, false)?; + } + Ok(()) + } + + fn insert_directive_name_references( + &self, + referencers: &mut Referencers, + name: &Name, + ) -> Result<(), FederationError> { + let directive_referencers = referencers.directives.get_mut(name).ok_or_else(|| { + SingleFederationError::Internal { + message: format!( + "Object type \"{}\"'s directive application \"@{}\" does not refer to an existing directive.", + self, + name, + ), + } + })?; + directive_referencers.object_types.insert(self.clone()); + Ok(()) + } + + fn remove_directive_name_references(&self, referencers: &mut Referencers, name: &str) { + let Some(directive_referencers) = referencers.directives.get_mut(name) else { + return; + }; + directive_referencers.object_types.shift_remove(self); + } + + fn insert_implements_interface_references( + &self, + referencers: &mut Referencers, + name: &Name, + ) -> Result<(), FederationError> { + let interface_type_referencers = referencers.interface_types.get_mut(name).ok_or_else(|| { + SingleFederationError::Internal { + message: format!( + "Object type \"{}\"'s implements \"{}\" does not refer to an existing interface.", + self, + name, + ), + } + })?; + interface_type_referencers.object_types.insert(self.clone()); + Ok(()) + } + + fn remove_implements_interface_references(&self, referencers: &mut Referencers, name: &str) { + let Some(interface_type_referencers) = referencers.interface_types.get_mut(name) else { + return; + }; + interface_type_referencers.object_types.shift_remove(self); + } + + fn insert_root_query_references( + &self, + schema: &Schema, + referencers: &mut Referencers, + ) -> Result<(), FederationError> { + // Note that unlike most insert logic in this file, the underlying elements being inserted + // here (the meta-fields __schema and __type) actually depend on two elements existing + // instead of one: the object type, and the schema root query type. This code is called at + // insertions for both of those elements, but needs to be able to handle if one doesn't + // exist, so accordingly we don't use get() below/we don't error if try_get() returns None. + let introspection_schema_field = self.introspection_schema_field(); + if let Some(field) = introspection_schema_field.try_get(schema) { + introspection_schema_field.insert_references(field, schema, referencers, true)?; + } + let introspection_type_field = self.introspection_type_field(); + if let Some(field) = introspection_type_field.try_get(schema) { + introspection_type_field.insert_references(field, schema, referencers, true)?; + } + Ok(()) + } + + fn remove_root_query_references( + &self, + schema: &Schema, + referencers: &mut Referencers, + ) -> Result<(), FederationError> { + let introspection_schema_field = self.introspection_schema_field(); + if let Some(field) = introspection_schema_field.try_get(schema) { + introspection_schema_field.remove_references(field, schema, referencers, true)?; + } + let introspection_type_field = self.introspection_type_field(); + if let Some(field) = introspection_type_field.try_get(schema) { + introspection_type_field.remove_references(field, schema, referencers, true)?; + } + Ok(()) + } +} + +impl Display for ObjectTypeDefinitionPosition { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.type_name) + } +} + +impl Debug for ObjectTypeDefinitionPosition { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "Object({self})") + } +} + +#[derive(Clone, PartialEq, Eq, Hash)] +pub(crate) struct ObjectFieldDefinitionPosition { + pub(crate) type_name: Name, + pub(crate) field_name: Name, +} + +impl ObjectFieldDefinitionPosition { + pub(crate) fn is_introspection_typename_field(&self) -> bool { + self.field_name == *INTROSPECTION_TYPENAME_FIELD_NAME + } + + pub(crate) fn parent(&self) -> ObjectTypeDefinitionPosition { + ObjectTypeDefinitionPosition { + type_name: self.type_name.clone(), + } + } + + pub(crate) fn argument(&self, argument_name: Name) -> ObjectFieldArgumentDefinitionPosition { + ObjectFieldArgumentDefinitionPosition { + type_name: self.type_name.clone(), + field_name: self.field_name.clone(), + argument_name, + } + } + + pub(crate) fn get<'schema>( + &self, + schema: &'schema Schema, + ) -> Result<&'schema Component, FederationError> { + let parent = self.parent(); + parent.get(schema)?; + + schema + .type_field(&self.type_name, &self.field_name) + .map_err(|_| { + SingleFederationError::Internal { + message: format!( + "Object type \"{}\" has no field \"{}\"", + parent, self.field_name + ), + } + .into() + }) + } + + pub(crate) fn try_get<'schema>( + &self, + schema: &'schema Schema, + ) -> Option<&'schema Component> { + self.get(schema).ok() + } + + fn make_mut<'schema>( + &self, + schema: &'schema mut Schema, + ) -> Result<&'schema mut Component, FederationError> { + let parent = self.parent(); + let type_ = parent.make_mut(schema)?.make_mut(); + + if is_graphql_reserved_name(&self.field_name) { + return Err(SingleFederationError::Internal { + message: format!("Cannot mutate reserved object field \"{}\"", self), + } + .into()); + } + type_.fields.get_mut(&self.field_name).ok_or_else(|| { + SingleFederationError::Internal { + message: format!( + "Object type \"{}\" has no field \"{}\"", + parent, self.field_name + ), + } + .into() + }) + } + + fn try_make_mut<'schema>( + &self, + schema: &'schema mut Schema, + ) -> Option<&'schema mut Component> { + if self.try_get(schema).is_some() { + self.make_mut(schema).ok() + } else { + None + } + } + + pub(crate) fn insert( + &self, + schema: &mut FederationSchema, + field: Component, + ) -> Result<(), FederationError> { + if self.field_name != field.name { + return Err(SingleFederationError::Internal { + message: format!( + "Object field \"{}\" given field named \"{}\"", + self, field.name, + ), + } + .into()); + } + if self.try_get(&schema.schema).is_some() { + return Err(SingleFederationError::Internal { + message: format!("Object field \"{}\" already exists in schema", self), + } + .into()); + } + self.parent() + .make_mut(&mut schema.schema)? + .make_mut() + .fields + .insert(self.field_name.clone(), field); + self.insert_references( + self.get(&schema.schema)?, + &schema.schema, + &mut schema.referencers, + false, + )?; + Ok(()) + } + + pub(crate) fn remove(&self, schema: &mut FederationSchema) -> Result<(), FederationError> { + let Some(field) = self.try_get(&schema.schema) else { + return Ok(()); + }; + self.remove_references(field, &schema.schema, &mut schema.referencers, false)?; + self.parent() + .make_mut(&mut schema.schema)? + .make_mut() + .fields + .shift_remove(&self.field_name); + Ok(()) + } + + pub(crate) fn remove_recursive( + &self, + schema: &mut FederationSchema, + ) -> Result<(), FederationError> { + self.remove(schema)?; + let parent = self.parent(); + let Some(type_) = parent.try_get(&schema.schema) else { + return Ok(()); + }; + if type_.fields.is_empty() { + parent.remove_recursive(schema)?; + } + Ok(()) + } + + pub(crate) fn insert_directive( + &self, + schema: &mut FederationSchema, + directive: Node, + ) -> Result<(), FederationError> { + let field = self.make_mut(&mut schema.schema)?; + if field + .directives + .iter() + .any(|other_directive| other_directive.ptr_eq(&directive)) + { + return Err(SingleFederationError::Internal { + message: format!( + "Directive application \"@{}\" already exists on object field \"{}\"", + directive.name, self, + ), + } + .into()); + } + let name = directive.name.clone(); + field.make_mut().directives.push(directive); + self.insert_directive_name_references(&mut schema.referencers, &name) + } + + pub(crate) fn remove_directive_name(&self, schema: &mut FederationSchema, name: &str) { + let Some(field) = self.try_make_mut(&mut schema.schema) else { + return; + }; + self.remove_directive_name_references(&mut schema.referencers, name); + field + .make_mut() + .directives + .retain(|other_directive| other_directive.name != name); + } + + pub(crate) fn remove_directive( + &self, + schema: &mut FederationSchema, + directive: &Node, + ) { + let Some(field) = self.try_make_mut(&mut schema.schema) else { + return; + }; + if !field.directives.iter().any(|other_directive| { + (other_directive.name == directive.name) && !other_directive.ptr_eq(directive) + }) { + self.remove_directive_name_references(&mut schema.referencers, &directive.name); + } + field + .make_mut() + .directives + .retain(|other_directive| !other_directive.ptr_eq(directive)); + } + + fn insert_references( + &self, + field: &Component, + schema: &Schema, + referencers: &mut Referencers, + allow_built_ins: bool, + ) -> Result<(), FederationError> { + if !allow_built_ins && is_graphql_reserved_name(&self.field_name) { + return Err(SingleFederationError::Internal { + message: format!("Cannot insert reserved object field \"{}\"", self), + } + .into()); + } + validate_node_directives(field.directives.deref())?; + for directive_reference in field.directives.iter() { + self.insert_directive_name_references(referencers, &directive_reference.name)?; + } + self.insert_type_references(field, schema, referencers)?; + validate_arguments(&field.arguments)?; + for argument in field.arguments.iter() { + self.argument(argument.name.clone()).insert_references( + argument, + schema, + referencers, + )?; + } + Ok(()) + } + + fn remove_references( + &self, + field: &Component, + schema: &Schema, + referencers: &mut Referencers, + allow_built_ins: bool, + ) -> Result<(), FederationError> { + if !allow_built_ins && is_graphql_reserved_name(&self.field_name) { + return Err(SingleFederationError::Internal { + message: format!("Cannot remove reserved object field \"{}\"", self), + } + .into()); + } + for directive_reference in field.directives.iter() { + self.remove_directive_name_references(referencers, &directive_reference.name); + } + self.remove_type_references(field, schema, referencers)?; + for argument in field.arguments.iter() { + self.argument(argument.name.clone()).remove_references( + argument, + schema, + referencers, + )?; + } + Ok(()) + } + + fn insert_directive_name_references( + &self, + referencers: &mut Referencers, + name: &Name, + ) -> Result<(), FederationError> { + let directive_referencers = referencers.directives.get_mut(name).ok_or_else(|| { + SingleFederationError::Internal { + message: format!( + "Object field \"{}\"'s directive application \"@{}\" does not refer to an existing directive.", + self, + name, + ), + } + })?; + directive_referencers.object_fields.insert(self.clone()); + Ok(()) + } + + fn remove_directive_name_references(&self, referencers: &mut Referencers, name: &str) { + let Some(directive_referencers) = referencers.directives.get_mut(name) else { + return; + }; + directive_referencers.object_fields.shift_remove(self); + } + + fn insert_type_references( + &self, + field: &Component, + schema: &Schema, + referencers: &mut Referencers, + ) -> Result<(), FederationError> { + let output_type_reference = field.ty.inner_named_type(); + match schema.types.get(output_type_reference) { + Some(ExtendedType::Scalar(_)) => { + let scalar_type_referencers = referencers + .scalar_types + .get_mut(output_type_reference) + .ok_or_else(|| SingleFederationError::Internal { + message: format!( + "Schema missing referencers for type \"{}\"", + output_type_reference + ), + })?; + scalar_type_referencers.object_fields.insert(self.clone()); + } + Some(ExtendedType::Object(_)) => { + let object_type_referencers = referencers + .object_types + .get_mut(output_type_reference) + .ok_or_else(|| SingleFederationError::Internal { + message: format!( + "Schema missing referencers for type \"{}\"", + output_type_reference + ), + })?; + object_type_referencers.object_fields.insert(self.clone()); + } + Some(ExtendedType::Interface(_)) => { + let interface_type_referencers = referencers + .interface_types + .get_mut(output_type_reference) + .ok_or_else(|| SingleFederationError::Internal { + message: format!( + "Schema missing referencers for type \"{}\"", + output_type_reference + ), + })?; + interface_type_referencers + .object_fields + .insert(self.clone()); + } + Some(ExtendedType::Union(_)) => { + let union_type_referencers = referencers + .union_types + .get_mut(output_type_reference) + .ok_or_else(|| SingleFederationError::Internal { + message: format!( + "Schema missing referencers for type \"{}\"", + output_type_reference + ), + })?; + union_type_referencers.object_fields.insert(self.clone()); + } + Some(ExtendedType::Enum(_)) => { + let enum_type_referencers = referencers + .enum_types + .get_mut(output_type_reference) + .ok_or_else(|| SingleFederationError::Internal { + message: format!( + "Schema missing referencers for type \"{}\"", + output_type_reference + ), + })?; + enum_type_referencers.object_fields.insert(self.clone()); + } + _ => { + return Err( + SingleFederationError::Internal { + message: format!( + "Object field \"{}\"'s inner type \"{}\" does not refer to an existing output type.", + self, + output_type_reference.deref(), + ) + }.into() + ); + } + } + Ok(()) + } + + fn remove_type_references( + &self, + field: &Component, + schema: &Schema, + referencers: &mut Referencers, + ) -> Result<(), FederationError> { + let output_type_reference = field.ty.inner_named_type(); + match schema.types.get(output_type_reference) { + Some(ExtendedType::Scalar(_)) => { + let Some(scalar_type_referencers) = + referencers.scalar_types.get_mut(output_type_reference) + else { + return Ok(()); + }; + scalar_type_referencers.object_fields.shift_remove(self); + } + Some(ExtendedType::Object(_)) => { + let Some(object_type_referencers) = + referencers.object_types.get_mut(output_type_reference) + else { + return Ok(()); + }; + object_type_referencers.object_fields.shift_remove(self); + } + Some(ExtendedType::Interface(_)) => { + let Some(interface_type_referencers) = + referencers.interface_types.get_mut(output_type_reference) + else { + return Ok(()); + }; + interface_type_referencers.object_fields.shift_remove(self); + } + Some(ExtendedType::Union(_)) => { + let Some(union_type_referencers) = + referencers.union_types.get_mut(output_type_reference) + else { + return Ok(()); + }; + union_type_referencers.object_fields.shift_remove(self); + } + Some(ExtendedType::Enum(_)) => { + let Some(enum_type_referencers) = + referencers.enum_types.get_mut(output_type_reference) + else { + return Ok(()); + }; + enum_type_referencers.object_fields.shift_remove(self); + } + _ => { + return Err( + SingleFederationError::Internal { + message: format!( + "Object field \"{}\"'s inner type \"{}\" does not refer to an existing output type.", + self, + output_type_reference.deref(), + ) + }.into() + ); + } + } + Ok(()) + } +} + +impl Display for ObjectFieldDefinitionPosition { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}.{}", self.type_name, self.field_name) + } +} + +impl Debug for ObjectFieldDefinitionPosition { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "ObjectField({self})") + } +} + +#[derive(Clone, PartialEq, Eq, Hash)] +pub(crate) struct ObjectFieldArgumentDefinitionPosition { + pub(crate) type_name: Name, + pub(crate) field_name: Name, + pub(crate) argument_name: Name, +} + +impl ObjectFieldArgumentDefinitionPosition { + pub(crate) fn parent(&self) -> ObjectFieldDefinitionPosition { + ObjectFieldDefinitionPosition { + type_name: self.type_name.clone(), + field_name: self.field_name.clone(), + } + } + + pub(crate) fn get<'schema>( + &self, + schema: &'schema Schema, + ) -> Result<&'schema Node, FederationError> { + let parent = self.parent(); + let type_ = parent.get(schema)?; + + type_ + .arguments + .iter() + .find(|a| a.name == self.argument_name) + .ok_or_else(|| { + SingleFederationError::Internal { + message: format!( + "Object field \"{}\" has no argument \"{}\"", + parent, self.argument_name + ), + } + .into() + }) + } + + pub(crate) fn try_get<'schema>( + &self, + schema: &'schema Schema, + ) -> Option<&'schema Node> { + self.get(schema).ok() + } + + fn make_mut<'schema>( + &self, + schema: &'schema mut Schema, + ) -> Result<&'schema mut Node, FederationError> { + let parent = self.parent(); + let type_ = parent.make_mut(schema)?.make_mut(); + + type_ + .arguments + .iter_mut() + .find(|a| a.name == self.argument_name) + .ok_or_else(|| { + SingleFederationError::Internal { + message: format!( + "Object field \"{}\" has no argument \"{}\"", + parent, self.argument_name + ), + } + .into() + }) + } + + fn try_make_mut<'schema>( + &self, + schema: &'schema mut Schema, + ) -> Option<&'schema mut Node> { + if self.try_get(schema).is_some() { + self.make_mut(schema).ok() + } else { + None + } + } + + pub(crate) fn insert( + &self, + schema: &mut FederationSchema, + argument: Node, + ) -> Result<(), FederationError> { + if self.argument_name != argument.name { + return Err(SingleFederationError::Internal { + message: format!( + "Object field argument \"{}\" given argument named \"{}\"", + self, argument.name, + ), + } + .into()); + } + if self.try_get(&schema.schema).is_some() { + // TODO: Handle old spec edge case of arguments with non-unique names + return Err(SingleFederationError::Internal { + message: format!( + "Object field argument \"{}\" already exists in schema", + self, + ), + } + .into()); + } + self.parent() + .make_mut(&mut schema.schema)? + .make_mut() + .arguments + .push(argument); + self.insert_references( + self.get(&schema.schema)?, + &schema.schema, + &mut schema.referencers, + ) + } + + pub(crate) fn remove(&self, schema: &mut FederationSchema) -> Result<(), FederationError> { + let Some(argument) = self.try_get(&schema.schema) else { + return Ok(()); + }; + self.remove_references(argument, &schema.schema, &mut schema.referencers)?; + self.parent() + .make_mut(&mut schema.schema)? + .make_mut() + .arguments + .retain(|other_argument| other_argument.name != self.argument_name); + Ok(()) + } + + pub(crate) fn insert_directive( + &self, + schema: &mut FederationSchema, + directive: Node, + ) -> Result<(), FederationError> { + let argument = self.make_mut(&mut schema.schema)?; + if argument + .directives + .iter() + .any(|other_directive| other_directive.ptr_eq(&directive)) + { + return Err(SingleFederationError::Internal { + message: format!( + "Directive application \"@{}\" already exists on object field argument \"{}\"", + directive.name, self, + ), + } + .into()); + } + let name = directive.name.clone(); + argument.make_mut().directives.push(directive); + self.insert_directive_name_references(&mut schema.referencers, &name) + } + + pub(crate) fn remove_directive_name(&self, schema: &mut FederationSchema, name: &str) { + let Some(argument) = self.try_make_mut(&mut schema.schema) else { + return; + }; + self.remove_directive_name_references(&mut schema.referencers, name); + argument + .make_mut() + .directives + .retain(|other_directive| other_directive.name != name); + } + + pub(crate) fn remove_directive( + &self, + schema: &mut FederationSchema, + directive: &Node, + ) { + let Some(argument) = self.try_make_mut(&mut schema.schema) else { + return; + }; + if !argument.directives.iter().any(|other_directive| { + (other_directive.name == directive.name) && !other_directive.ptr_eq(directive) + }) { + self.remove_directive_name_references(&mut schema.referencers, &directive.name); + } + argument + .make_mut() + .directives + .retain(|other_directive| !other_directive.ptr_eq(directive)); + } + + fn insert_references( + &self, + argument: &Node, + schema: &Schema, + referencers: &mut Referencers, + ) -> Result<(), FederationError> { + if is_graphql_reserved_name(&self.argument_name) { + return Err(SingleFederationError::Internal { + message: format!("Cannot insert reserved object field argument \"{}\"", self), + } + .into()); + } + validate_node_directives(argument.directives.deref())?; + for directive_reference in argument.directives.iter() { + self.insert_directive_name_references(referencers, &directive_reference.name)?; + } + self.insert_type_references(argument, schema, referencers) + } + + fn remove_references( + &self, + argument: &Node, + schema: &Schema, + referencers: &mut Referencers, + ) -> Result<(), FederationError> { + if is_graphql_reserved_name(&self.argument_name) { + return Err(SingleFederationError::Internal { + message: format!("Cannot remove reserved object field argument \"{}\"", self), + } + .into()); + } + for directive_reference in argument.directives.iter() { + self.remove_directive_name_references(referencers, &directive_reference.name); + } + self.remove_type_references(argument, schema, referencers) + } + + fn insert_directive_name_references( + &self, + referencers: &mut Referencers, + name: &Name, + ) -> Result<(), FederationError> { + let directive_referencers = referencers.directives.get_mut(name).ok_or_else(|| { + SingleFederationError::Internal { + message: format!( + "Object field argument \"{}\"'s directive application \"@{}\" does not refer to an existing directive.", + self, + name, + ), + } + })?; + directive_referencers + .object_field_arguments + .insert(self.clone()); + Ok(()) + } + + fn remove_directive_name_references(&self, referencers: &mut Referencers, name: &str) { + let Some(directive_referencers) = referencers.directives.get_mut(name) else { + return; + }; + directive_referencers + .object_field_arguments + .shift_remove(self); + } + + fn insert_type_references( + &self, + argument: &Node, + schema: &Schema, + referencers: &mut Referencers, + ) -> Result<(), FederationError> { + let input_type_reference = argument.ty.inner_named_type(); + match schema.types.get(input_type_reference) { + Some(ExtendedType::Scalar(_)) => { + let scalar_type_referencers = referencers + .scalar_types + .get_mut(input_type_reference) + .ok_or_else(|| SingleFederationError::Internal { + message: format!( + "Schema missing referencers for type \"{}\"", + input_type_reference + ), + })?; + scalar_type_referencers + .object_field_arguments + .insert(self.clone()); + } + Some(ExtendedType::Enum(_)) => { + let enum_type_referencers = referencers + .enum_types + .get_mut(input_type_reference) + .ok_or_else(|| SingleFederationError::Internal { + message: format!( + "Schema missing referencers for type \"{}\"", + input_type_reference + ), + })?; + enum_type_referencers + .object_field_arguments + .insert(self.clone()); + } + Some(ExtendedType::InputObject(_)) => { + let input_object_type_referencers = referencers + .input_object_types + .get_mut(input_type_reference) + .ok_or_else(|| SingleFederationError::Internal { + message: format!( + "Schema missing referencers for type \"{}\"", + input_type_reference + ), + })?; + input_object_type_referencers + .object_field_arguments + .insert(self.clone()); + } + _ => { + return Err( + SingleFederationError::Internal { + message: format!( + "Object field argument \"{}\"'s inner type \"{}\" does not refer to an existing input type.", + self, + input_type_reference.deref(), + ) + }.into() + ); + } + } + Ok(()) + } + + fn remove_type_references( + &self, + argument: &Node, + schema: &Schema, + referencers: &mut Referencers, + ) -> Result<(), FederationError> { + let input_type_reference = argument.ty.inner_named_type(); + match schema.types.get(input_type_reference) { + Some(ExtendedType::Scalar(_)) => { + let Some(scalar_type_referencers) = + referencers.scalar_types.get_mut(input_type_reference) + else { + return Ok(()); + }; + scalar_type_referencers + .object_field_arguments + .shift_remove(self); + } + Some(ExtendedType::Enum(_)) => { + let Some(enum_type_referencers) = + referencers.enum_types.get_mut(input_type_reference) + else { + return Ok(()); + }; + enum_type_referencers + .object_field_arguments + .shift_remove(self); + } + Some(ExtendedType::InputObject(_)) => { + let Some(input_object_type_referencers) = + referencers.input_object_types.get_mut(input_type_reference) + else { + return Ok(()); + }; + input_object_type_referencers + .object_field_arguments + .shift_remove(self); + } + _ => { + return Err( + SingleFederationError::Internal { + message: format!( + "Object field argument \"{}\"'s inner type \"{}\" does not refer to an existing input type.", + self, + input_type_reference.deref(), + ) + }.into() + ); + } + } + Ok(()) + } +} + +impl Display for ObjectFieldArgumentDefinitionPosition { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}.{}({}:)", + self.type_name, self.field_name, self.argument_name + ) + } +} + +impl Debug for ObjectFieldArgumentDefinitionPosition { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "ObjectFieldArgument({self})") + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub(crate) struct InterfaceTypeDefinitionPosition { + pub(crate) type_name: Name, +} + +impl InterfaceTypeDefinitionPosition { + pub(crate) fn new(type_name: Name) -> Self { + Self { type_name } + } + + pub(crate) fn field(&self, field_name: Name) -> InterfaceFieldDefinitionPosition { + InterfaceFieldDefinitionPosition { + type_name: self.type_name.clone(), + field_name, + } + } + + pub(crate) fn introspection_typename_field(&self) -> InterfaceFieldDefinitionPosition { + self.field(INTROSPECTION_TYPENAME_FIELD_NAME.clone()) + } + + pub(crate) fn get<'schema>( + &self, + schema: &'schema Schema, + ) -> Result<&'schema Node, FederationError> { + schema + .types + .get(&self.type_name) + .ok_or_else(|| { + SingleFederationError::Internal { + message: format!("Schema has no type \"{}\"", self), + } + .into() + }) + .and_then(|type_| { + if let ExtendedType::Interface(type_) = type_ { + Ok(type_) + } else { + Err(SingleFederationError::Internal { + message: format!("Schema type \"{}\" was not an interface", self), + } + .into()) + } + }) + } + + pub(crate) fn try_get<'schema>( + &self, + schema: &'schema Schema, + ) -> Option<&'schema Node> { + self.get(schema).ok() + } + + fn make_mut<'schema>( + &self, + schema: &'schema mut Schema, + ) -> Result<&'schema mut Node, FederationError> { + schema + .types + .get_mut(&self.type_name) + .ok_or_else(|| { + SingleFederationError::Internal { + message: format!("Schema has no type \"{}\"", self), + } + .into() + }) + .and_then(|type_| { + if let ExtendedType::Interface(type_) = type_ { + Ok(type_) + } else { + Err(SingleFederationError::Internal { + message: format!("Schema type \"{}\" was not an interface", self), + } + .into()) + } + }) + } + + fn try_make_mut<'schema>( + &self, + schema: &'schema mut Schema, + ) -> Option<&'schema mut Node> { + if self.try_get(schema).is_some() { + self.make_mut(schema).ok() + } else { + None + } + } + + pub(crate) fn pre_insert(&self, schema: &mut FederationSchema) -> Result<(), FederationError> { + if schema.referencers.contains_type_name(&self.type_name) { + // TODO: Allow built-in shadowing instead of ignoring them + if is_graphql_reserved_name(&self.type_name) { + return Ok(()); + } + return Err(SingleFederationError::Internal { + message: format!("Type \"{}\" has already been pre-inserted", self), + } + .into()); + } + schema + .referencers + .interface_types + .insert(self.type_name.clone(), Default::default()); + Ok(()) + } + + pub(crate) fn insert( + &self, + schema: &mut FederationSchema, + type_: Node, + ) -> Result<(), FederationError> { + if self.type_name != type_.name { + return Err(SingleFederationError::Internal { + message: format!( + "Interface type \"{}\" given type named \"{}\"", + self, type_.name, + ), + } + .into()); + } + if !schema + .referencers + .interface_types + .contains_key(&self.type_name) + { + return Err(SingleFederationError::Internal { + message: format!("Type \"{}\" has not been pre-inserted", self), + } + .into()); + } + if schema.schema.types.contains_key(&self.type_name) { + // TODO: Allow built-in shadowing instead of ignoring them + if is_graphql_reserved_name(&self.type_name) { + return Ok(()); + } + return Err(SingleFederationError::Internal { + message: format!("Type \"{}\" already exists in schema", self), + } + .into()); + } + schema + .schema + .types + .insert(self.type_name.clone(), ExtendedType::Interface(type_)); + self.insert_references( + self.get(&schema.schema)?, + &schema.schema, + &mut schema.referencers, + ) + } + + pub(crate) fn remove( + &self, + schema: &mut FederationSchema, + ) -> Result, FederationError> { + let Some(referencers) = self.remove_internal(schema)? else { + return Ok(None); + }; + for type_ in &referencers.object_types { + type_.remove_implements_interface(schema, &self.type_name); + } + for field in &referencers.object_fields { + field.remove(schema)?; + } + for type_ in &referencers.interface_types { + type_.remove_implements_interface(schema, &self.type_name); + } + for field in &referencers.interface_fields { + field.remove(schema)?; + } + Ok(Some(referencers)) + } + + pub(crate) fn remove_recursive( + &self, + schema: &mut FederationSchema, + ) -> Result<(), FederationError> { + let Some(referencers) = self.remove_internal(schema)? else { + return Ok(()); + }; + for type_ in referencers.object_types { + type_.remove_implements_interface(schema, &self.type_name); + } + for field in referencers.object_fields { + field.remove_recursive(schema)?; + } + for type_ in referencers.interface_types { + type_.remove_implements_interface(schema, &self.type_name); + } + for field in referencers.interface_fields { + field.remove_recursive(schema)?; + } + Ok(()) + } + + fn remove_internal( + &self, + schema: &mut FederationSchema, + ) -> Result, FederationError> { + let Some(type_) = self.try_get(&schema.schema) else { + return Ok(None); + }; + self.remove_references(type_, &schema.schema, &mut schema.referencers)?; + schema.schema.types.shift_remove(&self.type_name); + Ok(Some( + schema + .referencers + .interface_types + .shift_remove(&self.type_name) + .ok_or_else(|| SingleFederationError::Internal { + message: format!("Schema missing referencers for type \"{}\"", self), + })?, + )) + } + + pub(crate) fn insert_directive( + &self, + schema: &mut FederationSchema, + directive: Component, + ) -> Result<(), FederationError> { + let type_ = self.make_mut(&mut schema.schema)?; + if type_ + .directives + .iter() + .any(|other_directive| other_directive.ptr_eq(&directive)) + { + return Err(SingleFederationError::Internal { + message: format!( + "Directive application \"@{}\" already exists on interface type \"{}\"", + directive.name, self, + ), + } + .into()); + } + let name = directive.name.clone(); + type_.make_mut().directives.push(directive); + self.insert_directive_name_references(&mut schema.referencers, &name) + } + + pub(crate) fn remove_directive_name(&self, schema: &mut FederationSchema, name: &str) { + let Some(type_) = self.try_make_mut(&mut schema.schema) else { + return; + }; + self.remove_directive_name_references(&mut schema.referencers, name); + type_ + .make_mut() + .directives + .retain(|other_directive| other_directive.name != name); + } + + pub(crate) fn remove_directive( + &self, + schema: &mut FederationSchema, + directive: &Component, + ) { + let Some(type_) = self.try_make_mut(&mut schema.schema) else { + return; + }; + if !type_.directives.iter().any(|other_directive| { + (other_directive.name == directive.name) && !other_directive.ptr_eq(directive) + }) { + self.remove_directive_name_references(&mut schema.referencers, &directive.name); + } + type_ + .make_mut() + .directives + .retain(|other_directive| !other_directive.ptr_eq(directive)); + } + + pub(crate) fn insert_implements_interface( + &self, + schema: &mut FederationSchema, + name: ComponentName, + ) -> Result<(), FederationError> { + let type_ = self.make_mut(&mut schema.schema)?; + type_.make_mut().implements_interfaces.insert(name.clone()); + self.insert_implements_interface_references(&mut schema.referencers, &name) + } + + pub(crate) fn remove_implements_interface(&self, schema: &mut FederationSchema, name: &str) { + let Some(type_) = self.try_make_mut(&mut schema.schema) else { + return; + }; + self.remove_implements_interface_references(&mut schema.referencers, name); + type_ + .make_mut() + .implements_interfaces + .retain(|other_type| other_type != name); + } + + fn insert_references( + &self, + type_: &Node, + schema: &Schema, + referencers: &mut Referencers, + ) -> Result<(), FederationError> { + validate_component_directives(type_.directives.deref())?; + for directive_reference in type_.directives.iter() { + self.insert_directive_name_references(referencers, &directive_reference.name)?; + } + for interface_type_reference in type_.implements_interfaces.iter() { + self.insert_implements_interface_references( + referencers, + interface_type_reference.deref(), + )?; + } + let introspection_typename_field = self.introspection_typename_field(); + introspection_typename_field.insert_references( + introspection_typename_field.get(schema)?, + schema, + referencers, + true, + )?; + for (field_name, field) in type_.fields.iter() { + self.field(field_name.clone()) + .insert_references(field, schema, referencers, false)?; + } + Ok(()) + } + + fn remove_references( + &self, + type_: &Node, + schema: &Schema, + referencers: &mut Referencers, + ) -> Result<(), FederationError> { + for directive_reference in type_.directives.iter() { + self.remove_directive_name_references(referencers, &directive_reference.name); + } + for interface_type_reference in type_.implements_interfaces.iter() { + self.remove_implements_interface_references( + referencers, + interface_type_reference.deref(), + ); + } + let introspection_typename_field = self.introspection_typename_field(); + introspection_typename_field.remove_references( + introspection_typename_field.get(schema)?, + schema, + referencers, + true, + )?; + for (field_name, field) in type_.fields.iter() { + self.field(field_name.clone()) + .remove_references(field, schema, referencers, false)?; + } + Ok(()) + } + + fn insert_directive_name_references( + &self, + referencers: &mut Referencers, + name: &Name, + ) -> Result<(), FederationError> { + let directive_referencers = referencers.directives.get_mut(name).ok_or_else(|| { + SingleFederationError::Internal { + message: format!( + "Interface type \"{}\"'s directive application \"@{}\" does not refer to an existing directive.", + self, + name, + ), + } + })?; + directive_referencers.interface_types.insert(self.clone()); + Ok(()) + } + + fn remove_directive_name_references(&self, referencers: &mut Referencers, name: &str) { + let Some(directive_referencers) = referencers.directives.get_mut(name) else { + return; + }; + directive_referencers.interface_types.shift_remove(self); + } + + fn insert_implements_interface_references( + &self, + referencers: &mut Referencers, + name: &Name, + ) -> Result<(), FederationError> { + let interface_type_referencers = referencers.interface_types.get_mut(name).ok_or_else(|| { + SingleFederationError::Internal { + message: format!( + "Interface type \"{}\"'s implements \"{}\" does not refer to an existing interface.", + self, + name, + ), + } + })?; + interface_type_referencers + .interface_types + .insert(self.clone()); + Ok(()) + } + + fn remove_implements_interface_references(&self, referencers: &mut Referencers, name: &str) { + let Some(interface_type_referencers) = referencers.interface_types.get_mut(name) else { + return; + }; + interface_type_referencers + .interface_types + .shift_remove(self); + } +} + +impl Display for InterfaceTypeDefinitionPosition { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.type_name) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub(crate) struct InterfaceFieldDefinitionPosition { + pub(crate) type_name: Name, + pub(crate) field_name: Name, +} + +impl InterfaceFieldDefinitionPosition { + pub(crate) fn is_introspection_typename_field(&self) -> bool { + self.field_name == *INTROSPECTION_TYPENAME_FIELD_NAME + } + + pub(crate) fn parent(&self) -> InterfaceTypeDefinitionPosition { + InterfaceTypeDefinitionPosition { + type_name: self.type_name.clone(), + } + } + + pub(crate) fn argument(&self, argument_name: Name) -> InterfaceFieldArgumentDefinitionPosition { + InterfaceFieldArgumentDefinitionPosition { + type_name: self.type_name.clone(), + field_name: self.field_name.clone(), + argument_name, + } + } + + pub(crate) fn get<'schema>( + &self, + schema: &'schema Schema, + ) -> Result<&'schema Component, FederationError> { + let parent = self.parent(); + parent.get(schema)?; + + schema + .type_field(&self.type_name, &self.field_name) + .map_err(|_| { + SingleFederationError::Internal { + message: format!( + "Interface type \"{}\" has no field \"{}\"", + parent, self.field_name + ), + } + .into() + }) + } + + pub(crate) fn try_get<'schema>( + &self, + schema: &'schema Schema, + ) -> Option<&'schema Component> { + self.get(schema).ok() + } + + fn make_mut<'schema>( + &self, + schema: &'schema mut Schema, + ) -> Result<&'schema mut Component, FederationError> { + let parent = self.parent(); + let type_ = parent.make_mut(schema)?.make_mut(); + + if is_graphql_reserved_name(&self.field_name) { + return Err(SingleFederationError::Internal { + message: format!("Cannot mutate reserved interface field \"{}\"", self), + } + .into()); + } + type_.fields.get_mut(&self.field_name).ok_or_else(|| { + SingleFederationError::Internal { + message: format!( + "Interface type \"{}\" has no field \"{}\"", + parent, self.field_name + ), + } + .into() + }) + } + + fn try_make_mut<'schema>( + &self, + schema: &'schema mut Schema, + ) -> Option<&'schema mut Component> { + if self.try_get(schema).is_some() { + self.make_mut(schema).ok() + } else { + None + } + } + + pub(crate) fn insert( + &self, + schema: &mut FederationSchema, + field: Component, + ) -> Result<(), FederationError> { + if self.field_name != field.name { + return Err(SingleFederationError::Internal { + message: format!( + "Interface field \"{}\" given field named \"{}\"", + self, field.name, + ), + } + .into()); + } + if self.try_get(&schema.schema).is_some() { + return Err(SingleFederationError::Internal { + message: format!("Interface field \"{}\" already exists in schema", self), + } + .into()); + } + self.parent() + .make_mut(&mut schema.schema)? + .make_mut() + .fields + .insert(self.field_name.clone(), field); + self.insert_references( + self.get(&schema.schema)?, + &schema.schema, + &mut schema.referencers, + false, + ) + } + + pub(crate) fn remove(&self, schema: &mut FederationSchema) -> Result<(), FederationError> { + let Some(field) = self.try_get(&schema.schema) else { + return Ok(()); + }; + self.remove_references(field, &schema.schema, &mut schema.referencers, false)?; + self.parent() + .make_mut(&mut schema.schema)? + .make_mut() + .fields + .shift_remove(&self.field_name); + Ok(()) + } + + pub(crate) fn remove_recursive( + &self, + schema: &mut FederationSchema, + ) -> Result<(), FederationError> { + self.remove(schema)?; + let parent = self.parent(); + let Some(type_) = parent.try_get(&schema.schema) else { + return Ok(()); + }; + if type_.fields.is_empty() { + parent.remove_recursive(schema)?; + } + Ok(()) + } + + pub(crate) fn insert_directive( + &self, + schema: &mut FederationSchema, + directive: Node, + ) -> Result<(), FederationError> { + let field = self.make_mut(&mut schema.schema)?; + if field + .directives + .iter() + .any(|other_directive| other_directive.ptr_eq(&directive)) + { + return Err(SingleFederationError::Internal { + message: format!( + "Directive application \"@{}\" already exists on interface field \"{}\"", + directive.name, self, + ), + } + .into()); + } + let name = directive.name.clone(); + field.make_mut().directives.push(directive); + self.insert_directive_name_references(&mut schema.referencers, &name) + } + + pub(crate) fn remove_directive_name(&self, schema: &mut FederationSchema, name: &str) { + let Some(field) = self.try_make_mut(&mut schema.schema) else { + return; + }; + self.remove_directive_name_references(&mut schema.referencers, name); + field + .make_mut() + .directives + .retain(|other_directive| other_directive.name != name); + } + + pub(crate) fn remove_directive( + &self, + schema: &mut FederationSchema, + directive: &Node, + ) { + let Some(field) = self.try_make_mut(&mut schema.schema) else { + return; + }; + if !field.directives.iter().any(|other_directive| { + (other_directive.name == directive.name) && !other_directive.ptr_eq(directive) + }) { + self.remove_directive_name_references(&mut schema.referencers, &directive.name); + } + field + .make_mut() + .directives + .retain(|other_directive| !other_directive.ptr_eq(directive)); + } + + fn insert_references( + &self, + field: &Component, + schema: &Schema, + referencers: &mut Referencers, + allow_built_ins: bool, + ) -> Result<(), FederationError> { + if !allow_built_ins && is_graphql_reserved_name(&self.field_name) { + return Err(SingleFederationError::Internal { + message: format!("Cannot insert reserved interface field \"{}\"", self), + } + .into()); + } + validate_node_directives(field.directives.deref())?; + for directive_reference in field.directives.iter() { + self.insert_directive_name_references(referencers, &directive_reference.name)?; + } + self.insert_type_references(field, schema, referencers)?; + validate_arguments(&field.arguments)?; + for argument in field.arguments.iter() { + self.argument(argument.name.clone()).insert_references( + argument, + schema, + referencers, + )?; + } + Ok(()) + } + + fn remove_references( + &self, + field: &Component, + schema: &Schema, + referencers: &mut Referencers, + allow_built_ins: bool, + ) -> Result<(), FederationError> { + if !allow_built_ins && is_graphql_reserved_name(&self.field_name) { + return Err(SingleFederationError::Internal { + message: format!("Cannot remove reserved interface field \"{}\"", self), + } + .into()); + } + for directive_reference in field.directives.iter() { + self.remove_directive_name_references(referencers, &directive_reference.name); + } + self.remove_type_references(field, schema, referencers)?; + for argument in field.arguments.iter() { + self.argument(argument.name.clone()).remove_references( + argument, + schema, + referencers, + )?; + } + Ok(()) + } + + fn insert_directive_name_references( + &self, + referencers: &mut Referencers, + name: &Name, + ) -> Result<(), FederationError> { + let directive_referencers = referencers.directives.get_mut(name).ok_or_else(|| { + SingleFederationError::Internal { + message: format!( + "Interface field \"{}\"'s directive application \"@{}\" does not refer to an existing directive.", + self, + name, + ), + } + })?; + directive_referencers.interface_fields.insert(self.clone()); + Ok(()) + } + + fn remove_directive_name_references(&self, referencers: &mut Referencers, name: &str) { + let Some(directive_referencers) = referencers.directives.get_mut(name) else { + return; + }; + directive_referencers.interface_fields.shift_remove(self); + } + + fn insert_type_references( + &self, + field: &Component, + schema: &Schema, + referencers: &mut Referencers, + ) -> Result<(), FederationError> { + let output_type_reference = field.ty.inner_named_type(); + match schema.types.get(output_type_reference) { + Some(ExtendedType::Scalar(_)) => { + let scalar_type_referencers = referencers + .scalar_types + .get_mut(output_type_reference) + .ok_or_else(|| SingleFederationError::Internal { + message: format!( + "Schema missing referencers for type \"{}\"", + output_type_reference + ), + })?; + scalar_type_referencers + .interface_fields + .insert(self.clone()); + } + Some(ExtendedType::Object(_)) => { + let object_type_referencers = referencers + .object_types + .get_mut(output_type_reference) + .ok_or_else(|| SingleFederationError::Internal { + message: format!( + "Schema missing referencers for type \"{}\"", + output_type_reference + ), + })?; + object_type_referencers + .interface_fields + .insert(self.clone()); + } + Some(ExtendedType::Interface(_)) => { + let interface_type_referencers = referencers + .interface_types + .get_mut(output_type_reference) + .ok_or_else(|| SingleFederationError::Internal { + message: format!( + "Schema missing referencers for type \"{}\"", + output_type_reference + ), + })?; + interface_type_referencers + .interface_fields + .insert(self.clone()); + } + Some(ExtendedType::Union(_)) => { + let union_type_referencers = referencers + .union_types + .get_mut(output_type_reference) + .ok_or_else(|| SingleFederationError::Internal { + message: format!( + "Schema missing referencers for type \"{}\"", + output_type_reference + ), + })?; + union_type_referencers.interface_fields.insert(self.clone()); + } + Some(ExtendedType::Enum(_)) => { + let enum_type_referencers = referencers + .enum_types + .get_mut(output_type_reference) + .ok_or_else(|| SingleFederationError::Internal { + message: format!( + "Schema missing referencers for type \"{}\"", + output_type_reference + ), + })?; + enum_type_referencers.interface_fields.insert(self.clone()); + } + _ => { + return Err( + SingleFederationError::Internal { + message: format!( + "Interface field \"{}\"'s inner type \"{}\" does not refer to an existing output type.", + self, + output_type_reference.deref(), + ) + }.into() + ); + } + } + Ok(()) + } + + fn remove_type_references( + &self, + field: &Component, + schema: &Schema, + referencers: &mut Referencers, + ) -> Result<(), FederationError> { + let output_type_reference = field.ty.inner_named_type(); + match schema.types.get(output_type_reference) { + Some(ExtendedType::Scalar(_)) => { + let Some(scalar_type_referencers) = + referencers.scalar_types.get_mut(output_type_reference) + else { + return Ok(()); + }; + scalar_type_referencers.interface_fields.shift_remove(self); + } + Some(ExtendedType::Object(_)) => { + let Some(object_type_referencers) = + referencers.object_types.get_mut(output_type_reference) + else { + return Ok(()); + }; + object_type_referencers.interface_fields.shift_remove(self); + } + Some(ExtendedType::Interface(_)) => { + let Some(interface_type_referencers) = + referencers.interface_types.get_mut(output_type_reference) + else { + return Ok(()); + }; + interface_type_referencers + .interface_fields + .shift_remove(self); + } + Some(ExtendedType::Union(_)) => { + let Some(union_type_referencers) = + referencers.union_types.get_mut(output_type_reference) + else { + return Ok(()); + }; + union_type_referencers.interface_fields.shift_remove(self); + } + Some(ExtendedType::Enum(_)) => { + let Some(enum_type_referencers) = + referencers.enum_types.get_mut(output_type_reference) + else { + return Ok(()); + }; + enum_type_referencers.interface_fields.shift_remove(self); + } + _ => { + return Err( + SingleFederationError::Internal { + message: format!( + "Interface field \"{}\"'s inner type \"{}\" does not refer to an existing output type.", + self, + output_type_reference.deref(), + ) + }.into() + ); + } + } + Ok(()) + } +} + +impl Display for InterfaceFieldDefinitionPosition { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}.{}", self.type_name, self.field_name) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub(crate) struct InterfaceFieldArgumentDefinitionPosition { + pub(crate) type_name: Name, + pub(crate) field_name: Name, + pub(crate) argument_name: Name, +} + +impl InterfaceFieldArgumentDefinitionPosition { + pub(crate) fn parent(&self) -> InterfaceFieldDefinitionPosition { + InterfaceFieldDefinitionPosition { + type_name: self.type_name.clone(), + field_name: self.field_name.clone(), + } + } + + pub(crate) fn get<'schema>( + &self, + schema: &'schema Schema, + ) -> Result<&'schema Node, FederationError> { + let parent = self.parent(); + let type_ = parent.get(schema)?; + + type_ + .arguments + .iter() + .find(|a| a.name == self.argument_name) + .ok_or_else(|| { + SingleFederationError::Internal { + message: format!( + "Interface field \"{}\" has no argument \"{}\"", + parent, self.argument_name + ), + } + .into() + }) + } + + pub(crate) fn try_get<'schema>( + &self, + schema: &'schema Schema, + ) -> Option<&'schema Node> { + self.get(schema).ok() + } + + fn make_mut<'schema>( + &self, + schema: &'schema mut Schema, + ) -> Result<&'schema mut Node, FederationError> { + let parent = self.parent(); + let type_ = parent.make_mut(schema)?.make_mut(); + + type_ + .arguments + .iter_mut() + .find(|a| a.name == self.argument_name) + .ok_or_else(|| { + SingleFederationError::Internal { + message: format!( + "Interface field \"{}\" has no argument \"{}\"", + parent, self.argument_name + ), + } + .into() + }) + } + + fn try_make_mut<'schema>( + &self, + schema: &'schema mut Schema, + ) -> Option<&'schema mut Node> { + if self.try_get(schema).is_some() { + self.make_mut(schema).ok() + } else { + None + } + } + + pub(crate) fn insert( + &self, + schema: &mut FederationSchema, + argument: Node, + ) -> Result<(), FederationError> { + if self.argument_name != argument.name { + return Err(SingleFederationError::Internal { + message: format!( + "Interface field argument \"{}\" given argument named \"{}\"", + self, argument.name, + ), + } + .into()); + } + if self.try_get(&schema.schema).is_some() { + // TODO: Handle old spec edge case of arguments with non-unique names + return Err(SingleFederationError::Internal { + message: format!( + "Interface field argument \"{}\" already exists in schema", + self, + ), + } + .into()); + } + self.parent() + .make_mut(&mut schema.schema)? + .make_mut() + .arguments + .push(argument); + self.insert_references( + self.get(&schema.schema)?, + &schema.schema, + &mut schema.referencers, + ) + } + pub(crate) fn remove(&self, schema: &mut FederationSchema) -> Result<(), FederationError> { + let Some(argument) = self.try_get(&schema.schema) else { + return Ok(()); + }; + self.remove_references(argument, &schema.schema, &mut schema.referencers)?; + self.parent() + .make_mut(&mut schema.schema)? + .make_mut() + .arguments + .retain(|other_argument| other_argument.name != self.argument_name); + Ok(()) + } + + pub(crate) fn insert_directive( + &self, + schema: &mut FederationSchema, + directive: Node, + ) -> Result<(), FederationError> { + let argument = self.make_mut(&mut schema.schema)?; + if argument + .directives + .iter() + .any(|other_directive| other_directive.ptr_eq(&directive)) + { + return Err( + SingleFederationError::Internal { + message: format!( + "Directive application \"@{}\" already exists on interface field argument \"{}\"", + directive.name, + self, + ) + }.into() + ); + } + let name = directive.name.clone(); + argument.make_mut().directives.push(directive); + self.insert_directive_name_references(&mut schema.referencers, &name) + } + + pub(crate) fn remove_directive_name(&self, schema: &mut FederationSchema, name: &str) { + let Some(argument) = self.try_make_mut(&mut schema.schema) else { + return; + }; + self.remove_directive_name_references(&mut schema.referencers, name); + argument + .make_mut() + .directives + .retain(|other_directive| other_directive.name != name); + } + + pub(crate) fn remove_directive( + &self, + schema: &mut FederationSchema, + directive: &Node, + ) { + let Some(argument) = self.try_make_mut(&mut schema.schema) else { + return; + }; + if !argument.directives.iter().any(|other_directive| { + (other_directive.name == directive.name) && !other_directive.ptr_eq(directive) + }) { + self.remove_directive_name_references(&mut schema.referencers, &directive.name); + } + argument + .make_mut() + .directives + .retain(|other_directive| !other_directive.ptr_eq(directive)); + } + + fn insert_references( + &self, + argument: &Node, + schema: &Schema, + referencers: &mut Referencers, + ) -> Result<(), FederationError> { + if is_graphql_reserved_name(&self.argument_name) { + return Err(SingleFederationError::Internal { + message: format!( + "Cannot insert reserved interface field argument \"{}\"", + self + ), + } + .into()); + } + validate_node_directives(argument.directives.deref())?; + for directive_reference in argument.directives.iter() { + self.insert_directive_name_references(referencers, &directive_reference.name)?; + } + self.insert_type_references(argument, schema, referencers) + } + + fn remove_references( + &self, + argument: &Node, + schema: &Schema, + referencers: &mut Referencers, + ) -> Result<(), FederationError> { + if is_graphql_reserved_name(&self.argument_name) { + return Err(SingleFederationError::Internal { + message: format!( + "Cannot remove reserved interface field argument \"{}\"", + self + ), + } + .into()); + } + for directive_reference in argument.directives.iter() { + self.remove_directive_name_references(referencers, &directive_reference.name); + } + self.remove_type_references(argument, schema, referencers) + } + + fn insert_directive_name_references( + &self, + referencers: &mut Referencers, + name: &Name, + ) -> Result<(), FederationError> { + let directive_referencers = referencers.directives.get_mut(name).ok_or_else(|| { + SingleFederationError::Internal { + message: format!( + "Interface field argument \"{}\"'s directive application \"@{}\" does not refer to an existing directive.", + self, + name, + ), + } + })?; + directive_referencers + .interface_field_arguments + .insert(self.clone()); + Ok(()) + } + + fn remove_directive_name_references(&self, referencers: &mut Referencers, name: &str) { + let Some(directive_referencers) = referencers.directives.get_mut(name) else { + return; + }; + directive_referencers + .interface_field_arguments + .shift_remove(self); + } + + fn insert_type_references( + &self, + argument: &Node, + schema: &Schema, + referencers: &mut Referencers, + ) -> Result<(), FederationError> { + let input_type_reference = argument.ty.inner_named_type(); + match schema.types.get(input_type_reference) { + Some(ExtendedType::Scalar(_)) => { + let scalar_type_referencers = referencers + .scalar_types + .get_mut(input_type_reference) + .ok_or_else(|| SingleFederationError::Internal { + message: format!( + "Schema missing referencers for type \"{}\"", + input_type_reference + ), + })?; + scalar_type_referencers + .interface_field_arguments + .insert(self.clone()); + } + Some(ExtendedType::Enum(_)) => { + let enum_type_referencers = referencers + .enum_types + .get_mut(input_type_reference) + .ok_or_else(|| SingleFederationError::Internal { + message: format!( + "Schema missing referencers for type \"{}\"", + input_type_reference + ), + })?; + enum_type_referencers + .interface_field_arguments + .insert(self.clone()); + } + Some(ExtendedType::InputObject(_)) => { + let input_object_type_referencers = referencers + .input_object_types + .get_mut(input_type_reference) + .ok_or_else(|| SingleFederationError::Internal { + message: format!( + "Schema missing referencers for type \"{}\"", + input_type_reference + ), + })?; + input_object_type_referencers + .interface_field_arguments + .insert(self.clone()); + } + _ => { + return Err( + SingleFederationError::Internal { + message: format!( + "Interface field argument \"{}\"'s inner type \"{}\" does not refer to an existing input type.", + self, + input_type_reference.deref(), + ) + }.into() + ); + } + } + Ok(()) + } + + fn remove_type_references( + &self, + argument: &Node, + schema: &Schema, + referencers: &mut Referencers, + ) -> Result<(), FederationError> { + let input_type_reference = argument.ty.inner_named_type(); + match schema.types.get(input_type_reference) { + Some(ExtendedType::Scalar(_)) => { + let Some(scalar_type_referencers) = + referencers.scalar_types.get_mut(input_type_reference) + else { + return Ok(()); + }; + scalar_type_referencers + .interface_field_arguments + .shift_remove(self); + } + Some(ExtendedType::Enum(_)) => { + let Some(enum_type_referencers) = + referencers.enum_types.get_mut(input_type_reference) + else { + return Ok(()); + }; + enum_type_referencers + .interface_field_arguments + .shift_remove(self); + } + Some(ExtendedType::InputObject(_)) => { + let Some(input_object_type_referencers) = + referencers.input_object_types.get_mut(input_type_reference) + else { + return Ok(()); + }; + input_object_type_referencers + .interface_field_arguments + .shift_remove(self); + } + _ => { + return Err( + SingleFederationError::Internal { + message: format!( + "Interface field argument \"{}\"'s inner type \"{}\" does not refer to an existing input type.", + self, + input_type_reference.deref(), + ) + }.into() + ); + } + } + Ok(()) + } +} + +impl Display for InterfaceFieldArgumentDefinitionPosition { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}.{}({}:)", + self.type_name, self.field_name, self.argument_name + ) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub(crate) struct UnionTypeDefinitionPosition { + pub(crate) type_name: Name, +} + +impl UnionTypeDefinitionPosition { + pub(crate) fn new(type_name: Name) -> Self { + Self { type_name } + } + + pub(crate) fn introspection_typename_field(&self) -> UnionTypenameFieldDefinitionPosition { + UnionTypenameFieldDefinitionPosition { + type_name: self.type_name.clone(), + } + } + + pub(crate) fn get<'schema>( + &self, + schema: &'schema Schema, + ) -> Result<&'schema Node, FederationError> { + schema + .types + .get(&self.type_name) + .ok_or_else(|| { + SingleFederationError::Internal { + message: format!("Schema has no type \"{}\"", self), + } + .into() + }) + .and_then(|type_| { + if let ExtendedType::Union(type_) = type_ { + Ok(type_) + } else { + Err(SingleFederationError::Internal { + message: format!("Schema type \"{}\" was not an union", self), + } + .into()) + } + }) + } + + pub(crate) fn try_get<'schema>( + &self, + schema: &'schema Schema, + ) -> Option<&'schema Node> { + self.get(schema).ok() + } + + fn make_mut<'schema>( + &self, + schema: &'schema mut Schema, + ) -> Result<&'schema mut Node, FederationError> { + schema + .types + .get_mut(&self.type_name) + .ok_or_else(|| { + SingleFederationError::Internal { + message: format!("Schema has no type \"{}\"", self), + } + .into() + }) + .and_then(|type_| { + if let ExtendedType::Union(type_) = type_ { + Ok(type_) + } else { + Err(SingleFederationError::Internal { + message: format!("Schema type \"{}\" was not an union", self), + } + .into()) + } + }) + } + + fn try_make_mut<'schema>( + &self, + schema: &'schema mut Schema, + ) -> Option<&'schema mut Node> { + if self.try_get(schema).is_some() { + self.make_mut(schema).ok() + } else { + None + } + } + + pub(crate) fn pre_insert(&self, schema: &mut FederationSchema) -> Result<(), FederationError> { + if schema.referencers.contains_type_name(&self.type_name) { + // TODO: Allow built-in shadowing instead of ignoring them + if is_graphql_reserved_name(&self.type_name) { + return Ok(()); + } + return Err(SingleFederationError::Internal { + message: format!("Type \"{}\" has already been pre-inserted", self), + } + .into()); + } + schema + .referencers + .union_types + .insert(self.type_name.clone(), Default::default()); + Ok(()) + } + + pub(crate) fn insert( + &self, + schema: &mut FederationSchema, + type_: Node, + ) -> Result<(), FederationError> { + if self.type_name != type_.name { + return Err(SingleFederationError::Internal { + message: format!( + "Union type \"{}\" given type named \"{}\"", + self, type_.name, + ), + } + .into()); + } + if !schema.referencers.union_types.contains_key(&self.type_name) { + return Err(SingleFederationError::Internal { + message: format!("Type \"{}\" has not been pre-inserted", self), + } + .into()); + } + if schema.schema.types.contains_key(&self.type_name) { + // TODO: Allow built-in shadowing instead of ignoring them + if is_graphql_reserved_name(&self.type_name) { + return Ok(()); + } + return Err(SingleFederationError::Internal { + message: format!("Type \"{}\" already exists in schema", self), + } + .into()); + } + schema + .schema + .types + .insert(self.type_name.clone(), ExtendedType::Union(type_)); + self.insert_references(self.get(&schema.schema)?, &mut schema.referencers) + } + + pub(crate) fn remove( + &self, + schema: &mut FederationSchema, + ) -> Result, FederationError> { + let Some(referencers) = self.remove_internal(schema)? else { + return Ok(None); + }; + for field in &referencers.object_fields { + field.remove(schema)?; + } + for field in &referencers.interface_fields { + field.remove(schema)?; + } + Ok(Some(referencers)) + } + + pub(crate) fn remove_recursive( + &self, + schema: &mut FederationSchema, + ) -> Result<(), FederationError> { + let Some(referencers) = self.remove_internal(schema)? else { + return Ok(()); + }; + for field in referencers.object_fields { + field.remove_recursive(schema)?; + } + for field in referencers.interface_fields { + field.remove_recursive(schema)?; + } + Ok(()) + } + + fn remove_internal( + &self, + schema: &mut FederationSchema, + ) -> Result, FederationError> { + let Some(type_) = self.try_get(&schema.schema) else { + return Ok(None); + }; + self.remove_references(type_, &mut schema.referencers)?; + schema.schema.types.shift_remove(&self.type_name); + Ok(Some( + schema + .referencers + .union_types + .shift_remove(&self.type_name) + .ok_or_else(|| SingleFederationError::Internal { + message: format!("Schema missing referencers for type \"{}\"", self), + })?, + )) + } + + pub(crate) fn insert_directive( + &self, + schema: &mut FederationSchema, + directive: Component, + ) -> Result<(), FederationError> { + let type_ = self.make_mut(&mut schema.schema)?; + if type_ + .directives + .iter() + .any(|other_directive| other_directive.ptr_eq(&directive)) + { + return Err(SingleFederationError::Internal { + message: format!( + "Directive application \"@{}\" already exists on union type \"{}\"", + directive.name, self, + ), + } + .into()); + } + let name = directive.name.clone(); + type_.make_mut().directives.push(directive); + self.insert_directive_name_references(&mut schema.referencers, &name) + } + + pub(crate) fn remove_directive_name(&self, schema: &mut FederationSchema, name: &str) { + let Some(type_) = self.try_make_mut(&mut schema.schema) else { + return; + }; + self.remove_directive_name_references(&mut schema.referencers, name); + type_ + .make_mut() + .directives + .retain(|other_directive| other_directive.name != name); + } + + pub(crate) fn remove_directive( + &self, + schema: &mut FederationSchema, + directive: &Component, + ) { + let Some(type_) = self.try_make_mut(&mut schema.schema) else { + return; + }; + if !type_.directives.iter().any(|other_directive| { + (other_directive.name == directive.name) && !other_directive.ptr_eq(directive) + }) { + self.remove_directive_name_references(&mut schema.referencers, &directive.name); + } + type_ + .make_mut() + .directives + .retain(|other_directive| !other_directive.ptr_eq(directive)); + } + + pub(crate) fn insert_member( + &self, + schema: &mut FederationSchema, + name: ComponentName, + ) -> Result<(), FederationError> { + let type_ = self.make_mut(&mut schema.schema)?; + type_.make_mut().members.insert(name.clone()); + self.insert_member_references(&mut schema.referencers, &name) + } + + pub(crate) fn remove_member(&self, schema: &mut FederationSchema, name: &str) { + let Some(type_) = self.try_make_mut(&mut schema.schema) else { + return; + }; + self.remove_member_references(&mut schema.referencers, name); + type_ + .make_mut() + .members + .retain(|other_type| other_type != name); + } + + pub(crate) fn remove_member_recursive( + &self, + schema: &mut FederationSchema, + name: &str, + ) -> Result<(), FederationError> { + self.remove_member(schema, name); + let Some(type_) = self.try_get(&schema.schema) else { + return Ok(()); + }; + if type_.members.is_empty() { + self.remove_recursive(schema)?; + } + Ok(()) + } + + fn insert_references( + &self, + type_: &Node, + referencers: &mut Referencers, + ) -> Result<(), FederationError> { + validate_component_directives(type_.directives.deref())?; + for directive_reference in type_.directives.iter() { + self.insert_directive_name_references(referencers, &directive_reference.name)?; + } + for object_type_reference in type_.members.iter() { + self.insert_member_references(referencers, object_type_reference.deref())?; + } + self.introspection_typename_field() + .insert_references(referencers) + } + + fn remove_references( + &self, + type_: &Node, + referencers: &mut Referencers, + ) -> Result<(), FederationError> { + for directive_reference in type_.directives.iter() { + self.remove_directive_name_references(referencers, &directive_reference.name); + } + for object_type_reference in type_.members.iter() { + self.remove_member_references(referencers, object_type_reference.deref()); + } + self.introspection_typename_field() + .remove_references(referencers) + } + + fn insert_directive_name_references( + &self, + referencers: &mut Referencers, + name: &Name, + ) -> Result<(), FederationError> { + let directive_referencers = referencers.directives.get_mut(name).ok_or_else(|| { + SingleFederationError::Internal { + message: format!( + "Union type \"{}\"'s directive application \"@{}\" does not refer to an existing directive.", + self, + name, + ), + } + })?; + directive_referencers.union_types.insert(self.clone()); + Ok(()) + } + + fn remove_directive_name_references(&self, referencers: &mut Referencers, name: &str) { + let Some(directive_referencers) = referencers.directives.get_mut(name) else { + return; + }; + directive_referencers.union_types.shift_remove(self); + } + + fn insert_member_references( + &self, + referencers: &mut Referencers, + name: &Name, + ) -> Result<(), FederationError> { + let object_type_referencers = referencers.object_types.get_mut(name).ok_or_else(|| { + SingleFederationError::Internal { + message: format!( + "Union type \"{}\"'s member \"{}\" does not refer to an existing object.", + self, name, + ), + } + })?; + object_type_referencers.union_types.insert(self.clone()); + Ok(()) + } + + fn remove_member_references(&self, referencers: &mut Referencers, name: &str) { + let Some(object_type_referencers) = referencers.object_types.get_mut(name) else { + return; + }; + object_type_referencers.union_types.shift_remove(self); + } +} + +impl Display for UnionTypeDefinitionPosition { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.type_name) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub(crate) struct UnionTypenameFieldDefinitionPosition { + pub(crate) type_name: Name, +} + +impl UnionTypenameFieldDefinitionPosition { + pub(crate) fn field_name(&self) -> &Name { + &INTROSPECTION_TYPENAME_FIELD_NAME + } + + pub(crate) fn parent(&self) -> UnionTypeDefinitionPosition { + UnionTypeDefinitionPosition { + type_name: self.type_name.clone(), + } + } + + pub(crate) fn get<'schema>( + &self, + schema: &'schema Schema, + ) -> Result<&'schema Component, FederationError> { + let parent = self.parent(); + parent.get(schema)?; + + schema + .type_field(&self.type_name, self.field_name()) + .map_err(|_| { + SingleFederationError::Internal { + message: format!( + "Union type \"{}\" has no field \"{}\"", + parent, + self.field_name() + ), + } + .into() + }) + } + + pub(crate) fn try_get<'schema>( + &self, + schema: &'schema Schema, + ) -> Option<&'schema Component> { + self.get(schema).ok() + } + + fn insert_references(&self, referencers: &mut Referencers) -> Result<(), FederationError> { + self.insert_type_references(referencers)?; + Ok(()) + } + + fn remove_references(&self, referencers: &mut Referencers) -> Result<(), FederationError> { + self.remove_type_references(referencers)?; + Ok(()) + } + + fn insert_type_references(&self, referencers: &mut Referencers) -> Result<(), FederationError> { + let output_type_reference = "String"; + let scalar_type_referencers = referencers + .scalar_types + .get_mut(output_type_reference) + .ok_or_else(|| SingleFederationError::Internal { + message: format!( + "Schema missing referencers for type \"{}\"", + output_type_reference + ), + })?; + scalar_type_referencers.union_fields.insert(self.clone()); + Ok(()) + } + + fn remove_type_references(&self, referencers: &mut Referencers) -> Result<(), FederationError> { + let output_type_reference = "String"; + let Some(scalar_type_referencers) = referencers.scalar_types.get_mut(output_type_reference) + else { + return Ok(()); + }; + scalar_type_referencers.union_fields.shift_remove(self); + Ok(()) + } +} + +impl Display for UnionTypenameFieldDefinitionPosition { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}.{}", self.type_name, self.field_name()) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub(crate) struct EnumTypeDefinitionPosition { + pub(crate) type_name: Name, +} + +impl EnumTypeDefinitionPosition { + pub(crate) fn value(&self, value_name: Name) -> EnumValueDefinitionPosition { + EnumValueDefinitionPosition { + type_name: self.type_name.clone(), + value_name, + } + } + + pub(crate) fn get<'schema>( + &self, + schema: &'schema Schema, + ) -> Result<&'schema Node, FederationError> { + schema + .types + .get(&self.type_name) + .ok_or_else(|| { + SingleFederationError::Internal { + message: format!("Schema has no type \"{}\"", self), + } + .into() + }) + .and_then(|type_| { + if let ExtendedType::Enum(type_) = type_ { + Ok(type_) + } else { + Err(SingleFederationError::Internal { + message: format!("Schema type \"{}\" was not an enum", self), + } + .into()) + } + }) + } + + pub(crate) fn try_get<'schema>( + &self, + schema: &'schema Schema, + ) -> Option<&'schema Node> { + self.get(schema).ok() + } + + fn make_mut<'schema>( + &self, + schema: &'schema mut Schema, + ) -> Result<&'schema mut Node, FederationError> { + schema + .types + .get_mut(&self.type_name) + .ok_or_else(|| { + SingleFederationError::Internal { + message: format!("Schema has no type \"{}\"", self), + } + .into() + }) + .and_then(|type_| { + if let ExtendedType::Enum(type_) = type_ { + Ok(type_) + } else { + Err(SingleFederationError::Internal { + message: format!("Schema type \"{}\" was not an enum", self), + } + .into()) + } + }) + } + + fn try_make_mut<'schema>( + &self, + schema: &'schema mut Schema, + ) -> Option<&'schema mut Node> { + if self.try_get(schema).is_some() { + self.make_mut(schema).ok() + } else { + None + } + } + + pub(crate) fn pre_insert(&self, schema: &mut FederationSchema) -> Result<(), FederationError> { + if schema.referencers.contains_type_name(&self.type_name) { + // TODO: Allow built-in shadowing instead of ignoring them + if is_graphql_reserved_name(&self.type_name) { + return Ok(()); + } + return Err(SingleFederationError::Internal { + message: format!("Type \"{}\" has already been pre-inserted", self), + } + .into()); + } + schema + .referencers + .enum_types + .insert(self.type_name.clone(), Default::default()); + Ok(()) + } + + pub(crate) fn insert( + &self, + schema: &mut FederationSchema, + type_: Node, + ) -> Result<(), FederationError> { + if self.type_name != type_.name { + return Err(SingleFederationError::Internal { + message: format!("Enum type \"{}\" given type named \"{}\"", self, type_.name,), + } + .into()); + } + if !schema.referencers.enum_types.contains_key(&self.type_name) { + return Err(SingleFederationError::Internal { + message: format!("Type \"{}\" has not been pre-inserted", self), + } + .into()); + } + if schema.schema.types.contains_key(&self.type_name) { + // TODO: Allow built-in shadowing instead of ignoring them + if is_graphql_reserved_name(&self.type_name) { + return Ok(()); + } + return Err(SingleFederationError::Internal { + message: format!("Type \"{}\" already exists in schema", self), + } + .into()); + } + schema + .schema + .types + .insert(self.type_name.clone(), ExtendedType::Enum(type_)); + self.insert_references(self.get(&schema.schema)?, &mut schema.referencers) + } + + pub(crate) fn remove( + &self, + schema: &mut FederationSchema, + ) -> Result, FederationError> { + let Some(referencers) = self.remove_internal(schema)? else { + return Ok(None); + }; + for field in &referencers.object_fields { + field.remove(schema)?; + } + for argument in &referencers.object_field_arguments { + argument.remove(schema)?; + } + for field in &referencers.interface_fields { + field.remove(schema)?; + } + for argument in &referencers.interface_field_arguments { + argument.remove(schema)?; + } + for field in &referencers.input_object_fields { + field.remove(schema)?; + } + for argument in &referencers.directive_arguments { + argument.remove(schema)?; + } + Ok(Some(referencers)) + } + + pub(crate) fn remove_recursive( + &self, + schema: &mut FederationSchema, + ) -> Result<(), FederationError> { + let Some(referencers) = self.remove_internal(schema)? else { + return Ok(()); + }; + for field in referencers.object_fields { + field.remove_recursive(schema)?; + } + for argument in referencers.object_field_arguments { + argument.remove(schema)?; + } + for field in referencers.interface_fields { + field.remove_recursive(schema)?; + } + for argument in referencers.interface_field_arguments { + argument.remove(schema)?; + } + for field in referencers.input_object_fields { + field.remove_recursive(schema)?; + } + for argument in referencers.directive_arguments { + argument.remove(schema)?; + } + Ok(()) + } + + fn remove_internal( + &self, + schema: &mut FederationSchema, + ) -> Result, FederationError> { + let Some(type_) = self.try_get(&schema.schema) else { + return Ok(None); + }; + self.remove_references(type_, &mut schema.referencers)?; + schema.schema.types.shift_remove(&self.type_name); + Ok(Some( + schema + .referencers + .enum_types + .shift_remove(&self.type_name) + .ok_or_else(|| SingleFederationError::Internal { + message: format!("Schema missing referencers for type \"{}\"", self), + })?, + )) + } + + pub(crate) fn insert_directive( + &self, + schema: &mut FederationSchema, + directive: Component, + ) -> Result<(), FederationError> { + let type_ = self.make_mut(&mut schema.schema)?; + if type_ + .directives + .iter() + .any(|other_directive| other_directive.ptr_eq(&directive)) + { + return Err(SingleFederationError::Internal { + message: format!( + "Directive application \"@{}\" already exists on enum type \"{}\"", + directive.name, self, + ), + } + .into()); + } + let name = directive.name.clone(); + type_.make_mut().directives.push(directive); + self.insert_directive_name_references(&mut schema.referencers, &name) + } + + pub(crate) fn remove_directive_name(&self, schema: &mut FederationSchema, name: &str) { + let Some(type_) = self.try_make_mut(&mut schema.schema) else { + return; + }; + self.remove_directive_name_references(&mut schema.referencers, name); + type_ + .make_mut() + .directives + .retain(|other_directive| other_directive.name != name); + } + + pub(crate) fn remove_directive( + &self, + schema: &mut FederationSchema, + directive: &Component, + ) { + let Some(type_) = self.try_make_mut(&mut schema.schema) else { + return; + }; + if !type_.directives.iter().any(|other_directive| { + (other_directive.name == directive.name) && !other_directive.ptr_eq(directive) + }) { + self.remove_directive_name_references(&mut schema.referencers, &directive.name); + } + type_ + .make_mut() + .directives + .retain(|other_directive| !other_directive.ptr_eq(directive)); + } + + fn insert_references( + &self, + type_: &Node, + referencers: &mut Referencers, + ) -> Result<(), FederationError> { + validate_component_directives(type_.directives.deref())?; + for directive_reference in type_.directives.iter() { + self.insert_directive_name_references(referencers, &directive_reference.name)?; + } + for (value_name, value) in type_.values.iter() { + self.value(value_name.clone()) + .insert_references(value, referencers)?; + } + Ok(()) + } + + fn remove_references( + &self, + type_: &Node, + referencers: &mut Referencers, + ) -> Result<(), FederationError> { + for directive_reference in type_.directives.iter() { + self.remove_directive_name_references(referencers, &directive_reference.name); + } + for (value_name, value) in type_.values.iter() { + self.value(value_name.clone()) + .remove_references(value, referencers)?; + } + Ok(()) + } + + fn insert_directive_name_references( + &self, + referencers: &mut Referencers, + name: &Name, + ) -> Result<(), FederationError> { + let directive_referencers = referencers.directives.get_mut(name).ok_or_else(|| { + SingleFederationError::Internal { + message: format!( + "Enum type \"{}\"'s directive application \"@{}\" does not refer to an existing directive.", + self, + name, + ), + } + })?; + directive_referencers.enum_types.insert(self.clone()); + Ok(()) + } + + fn remove_directive_name_references(&self, referencers: &mut Referencers, name: &str) { + let Some(directive_referencers) = referencers.directives.get_mut(name) else { + return; + }; + directive_referencers.enum_types.shift_remove(self); + } +} + +impl Display for EnumTypeDefinitionPosition { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.type_name) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub(crate) struct EnumValueDefinitionPosition { + pub(crate) type_name: Name, + pub(crate) value_name: Name, +} + +impl EnumValueDefinitionPosition { + pub(crate) fn parent(&self) -> EnumTypeDefinitionPosition { + EnumTypeDefinitionPosition { + type_name: self.type_name.clone(), + } + } + + pub(crate) fn get<'schema>( + &self, + schema: &'schema Schema, + ) -> Result<&'schema Component, FederationError> { + let parent = self.parent(); + let type_ = parent.get(schema)?; + + type_.values.get(&self.value_name).ok_or_else(|| { + SingleFederationError::Internal { + message: format!( + "Enum type \"{}\" has no value \"{}\"", + parent, self.value_name + ), + } + .into() + }) + } + + pub(crate) fn try_get<'schema>( + &self, + schema: &'schema Schema, + ) -> Option<&'schema Component> { + self.get(schema).ok() + } + + fn make_mut<'schema>( + &self, + schema: &'schema mut Schema, + ) -> Result<&'schema mut Component, FederationError> { + let parent = self.parent(); + let type_ = parent.make_mut(schema)?.make_mut(); + + type_.values.get_mut(&self.value_name).ok_or_else(|| { + SingleFederationError::Internal { + message: format!( + "Enum type \"{}\" has no value \"{}\"", + parent, self.value_name + ), + } + .into() + }) + } + + fn try_make_mut<'schema>( + &self, + schema: &'schema mut Schema, + ) -> Option<&'schema mut Component> { + if self.try_get(schema).is_some() { + self.make_mut(schema).ok() + } else { + None + } + } + + pub(crate) fn insert( + &self, + schema: &mut FederationSchema, + value: Component, + ) -> Result<(), FederationError> { + if self.value_name != value.value { + return Err(SingleFederationError::Internal { + message: format!( + "Enum value \"{}\" given argument named \"{}\"", + self, value.value, + ), + } + .into()); + } + if self.try_get(&schema.schema).is_some() { + return Err(SingleFederationError::Internal { + message: format!("Enum value \"{}\" already exists in schema", self,), + } + .into()); + } + self.parent() + .make_mut(&mut schema.schema)? + .make_mut() + .values + .insert(self.value_name.clone(), value); + self.insert_references(self.get(&schema.schema)?, &mut schema.referencers) + } + + pub(crate) fn remove(&self, schema: &mut FederationSchema) -> Result<(), FederationError> { + let Some(value) = self.try_get(&schema.schema) else { + return Ok(()); + }; + self.remove_references(value, &mut schema.referencers)?; + self.parent() + .make_mut(&mut schema.schema)? + .make_mut() + .values + .shift_remove(&self.value_name); + Ok(()) + } + + pub(crate) fn remove_recursive( + &self, + schema: &mut FederationSchema, + ) -> Result<(), FederationError> { + self.remove(schema)?; + let parent = self.parent(); + let Some(type_) = parent.try_get(&schema.schema) else { + return Ok(()); + }; + if type_.values.is_empty() { + parent.remove_recursive(schema)?; + } + Ok(()) + } + + pub(crate) fn insert_directive( + &self, + schema: &mut FederationSchema, + directive: Node, + ) -> Result<(), FederationError> { + let value = self.make_mut(&mut schema.schema)?; + if value + .directives + .iter() + .any(|other_directive| other_directive.ptr_eq(&directive)) + { + return Err(SingleFederationError::Internal { + message: format!( + "Directive application \"@{}\" already exists on enum value \"{}\"", + directive.name, self, + ), + } + .into()); + } + let name = directive.name.clone(); + value.make_mut().directives.push(directive); + self.insert_directive_name_references(&mut schema.referencers, &name) + } + + pub(crate) fn remove_directive_name(&self, schema: &mut FederationSchema, name: &str) { + let Some(value) = self.try_make_mut(&mut schema.schema) else { + return; + }; + self.remove_directive_name_references(&mut schema.referencers, name); + value + .make_mut() + .directives + .retain(|other_directive| other_directive.name != name); + } + + pub(crate) fn remove_directive( + &self, + schema: &mut FederationSchema, + directive: &Node, + ) { + let Some(value) = self.try_make_mut(&mut schema.schema) else { + return; + }; + if !value.directives.iter().any(|other_directive| { + (other_directive.name == directive.name) && !other_directive.ptr_eq(directive) + }) { + self.remove_directive_name_references(&mut schema.referencers, &directive.name); + } + value + .make_mut() + .directives + .retain(|other_directive| !other_directive.ptr_eq(directive)); + } + + fn insert_references( + &self, + value: &Component, + referencers: &mut Referencers, + ) -> Result<(), FederationError> { + if is_graphql_reserved_name(&self.value_name) { + return Err(SingleFederationError::Internal { + message: format!("Cannot insert reserved enum value \"{}\"", self), + } + .into()); + } + validate_node_directives(value.directives.deref())?; + for directive_reference in value.directives.iter() { + self.insert_directive_name_references(referencers, &directive_reference.name)?; + } + Ok(()) + } + + fn remove_references( + &self, + value: &Component, + referencers: &mut Referencers, + ) -> Result<(), FederationError> { + if is_graphql_reserved_name(&self.value_name) { + return Err(SingleFederationError::Internal { + message: format!("Cannot remove reserved enum value \"{}\"", self), + } + .into()); + } + for directive_reference in value.directives.iter() { + self.remove_directive_name_references(referencers, &directive_reference.name); + } + Ok(()) + } + + fn insert_directive_name_references( + &self, + referencers: &mut Referencers, + name: &Name, + ) -> Result<(), FederationError> { + let directive_referencers = referencers.directives.get_mut(name).ok_or_else(|| { + SingleFederationError::Internal { + message: format!( + "Enum value \"{}\"'s directive application \"@{}\" does not refer to an existing directive.", + self, + name, + ), + } + })?; + directive_referencers.enum_values.insert(self.clone()); + Ok(()) + } + + fn remove_directive_name_references(&self, referencers: &mut Referencers, name: &str) { + let Some(directive_referencers) = referencers.directives.get_mut(name) else { + return; + }; + directive_referencers.enum_values.shift_remove(self); + } +} + +impl Display for EnumValueDefinitionPosition { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}.{}", self.type_name, self.value_name) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub(crate) struct InputObjectTypeDefinitionPosition { + pub(crate) type_name: Name, +} + +impl InputObjectTypeDefinitionPosition { + pub(crate) fn field(&self, field_name: Name) -> InputObjectFieldDefinitionPosition { + InputObjectFieldDefinitionPosition { + type_name: self.type_name.clone(), + field_name, + } + } + + pub(crate) fn get<'schema>( + &self, + schema: &'schema Schema, + ) -> Result<&'schema Node, FederationError> { + schema + .types + .get(&self.type_name) + .ok_or_else(|| { + SingleFederationError::Internal { + message: format!("Schema has no type \"{}\"", self), + } + .into() + }) + .and_then(|type_| { + if let ExtendedType::InputObject(type_) = type_ { + Ok(type_) + } else { + Err(SingleFederationError::Internal { + message: format!("Schema type \"{}\" was not an input object", self), + } + .into()) + } + }) + } + + pub(crate) fn try_get<'schema>( + &self, + schema: &'schema Schema, + ) -> Option<&'schema Node> { + self.get(schema).ok() + } + + fn make_mut<'schema>( + &self, + schema: &'schema mut Schema, + ) -> Result<&'schema mut Node, FederationError> { + schema + .types + .get_mut(&self.type_name) + .ok_or_else(|| { + SingleFederationError::Internal { + message: format!("Schema has no type \"{}\"", self), + } + .into() + }) + .and_then(|type_| { + if let ExtendedType::InputObject(type_) = type_ { + Ok(type_) + } else { + Err(SingleFederationError::Internal { + message: format!("Schema type \"{}\" was not an input object", self), + } + .into()) + } + }) + } + + fn try_make_mut<'schema>( + &self, + schema: &'schema mut Schema, + ) -> Option<&'schema mut Node> { + if self.try_get(schema).is_some() { + self.make_mut(schema).ok() + } else { + None + } + } + + pub(crate) fn pre_insert(&self, schema: &mut FederationSchema) -> Result<(), FederationError> { + if schema.referencers.contains_type_name(&self.type_name) { + // TODO: Allow built-in shadowing instead of ignoring them + if is_graphql_reserved_name(&self.type_name) { + return Ok(()); + } + return Err(SingleFederationError::Internal { + message: format!("Type \"{}\" has already been pre-inserted", self), + } + .into()); + } + schema + .referencers + .input_object_types + .insert(self.type_name.clone(), Default::default()); + Ok(()) + } + + pub(crate) fn insert( + &self, + schema: &mut FederationSchema, + type_: Node, + ) -> Result<(), FederationError> { + if self.type_name != type_.name { + return Err(SingleFederationError::Internal { + message: format!( + "Input object type \"{}\" given type named \"{}\"", + self, type_.name, + ), + } + .into()); + } + if !schema + .referencers + .input_object_types + .contains_key(&self.type_name) + { + return Err(SingleFederationError::Internal { + message: format!("Type \"{}\" has not been pre-inserted", self), + } + .into()); + } + if schema.schema.types.contains_key(&self.type_name) { + // TODO: Allow built-in shadowing instead of ignoring them + if is_graphql_reserved_name(&self.type_name) { + return Ok(()); + } + return Err(SingleFederationError::Internal { + message: format!("Type \"{}\" already exists in schema", self), + } + .into()); + } + schema + .schema + .types + .insert(self.type_name.clone(), ExtendedType::InputObject(type_)); + self.insert_references( + self.get(&schema.schema)?, + &schema.schema, + &mut schema.referencers, + ) + } + + pub(crate) fn remove( + &self, + schema: &mut FederationSchema, + ) -> Result, FederationError> { + let Some(referencers) = self.remove_internal(schema)? else { + return Ok(None); + }; + for argument in &referencers.object_field_arguments { + argument.remove(schema)?; + } + for argument in &referencers.interface_field_arguments { + argument.remove(schema)?; + } + for field in &referencers.input_object_fields { + field.remove(schema)?; + } + for argument in &referencers.directive_arguments { + argument.remove(schema)?; + } + Ok(Some(referencers)) + } + + pub(crate) fn remove_recursive( + &self, + schema: &mut FederationSchema, + ) -> Result<(), FederationError> { + let Some(referencers) = self.remove_internal(schema)? else { + return Ok(()); + }; + for argument in referencers.object_field_arguments { + argument.remove(schema)?; + } + for argument in referencers.interface_field_arguments { + argument.remove(schema)?; + } + for field in referencers.input_object_fields { + field.remove_recursive(schema)?; + } + for argument in referencers.directive_arguments { + argument.remove(schema)?; + } + Ok(()) + } + + fn remove_internal( + &self, + schema: &mut FederationSchema, + ) -> Result, FederationError> { + let Some(type_) = self.try_get(&schema.schema) else { + return Ok(None); + }; + self.remove_references(type_, &schema.schema, &mut schema.referencers)?; + schema.schema.types.shift_remove(&self.type_name); + Ok(Some( + schema + .referencers + .input_object_types + .shift_remove(&self.type_name) + .ok_or_else(|| SingleFederationError::Internal { + message: format!("Schema missing referencers for type \"{}\"", self), + })?, + )) + } + + pub(crate) fn insert_directive( + &self, + schema: &mut FederationSchema, + directive: Component, + ) -> Result<(), FederationError> { + let type_ = self.make_mut(&mut schema.schema)?; + if type_ + .directives + .iter() + .any(|other_directive| other_directive.ptr_eq(&directive)) + { + return Err(SingleFederationError::Internal { + message: format!( + "Directive application \"@{}\" already exists on input object type \"{}\"", + directive.name, self, + ), + } + .into()); + } + let name = directive.name.clone(); + type_.make_mut().directives.push(directive); + self.insert_directive_name_references(&mut schema.referencers, &name) + } + + pub(crate) fn remove_directive_name(&self, schema: &mut FederationSchema, name: &str) { + let Some(type_) = self.try_make_mut(&mut schema.schema) else { + return; + }; + self.remove_directive_name_references(&mut schema.referencers, name); + type_ + .make_mut() + .directives + .retain(|other_directive| other_directive.name != name); + } + + pub(crate) fn remove_directive( + &self, + schema: &mut FederationSchema, + directive: &Component, + ) { + let Some(type_) = self.try_make_mut(&mut schema.schema) else { + return; + }; + if !type_.directives.iter().any(|other_directive| { + (other_directive.name == directive.name) && !other_directive.ptr_eq(directive) + }) { + self.remove_directive_name_references(&mut schema.referencers, &directive.name); + } + type_ + .make_mut() + .directives + .retain(|other_directive| !other_directive.ptr_eq(directive)); + } + + fn insert_references( + &self, + type_: &Node, + schema: &Schema, + referencers: &mut Referencers, + ) -> Result<(), FederationError> { + validate_component_directives(type_.directives.deref())?; + for directive_reference in type_.directives.iter() { + self.insert_directive_name_references(referencers, &directive_reference.name)?; + } + for (field_name, field) in type_.fields.iter() { + self.field(field_name.clone()) + .insert_references(field, schema, referencers)?; + } + Ok(()) + } + + fn remove_references( + &self, + type_: &Node, + schema: &Schema, + referencers: &mut Referencers, + ) -> Result<(), FederationError> { + for directive_reference in type_.directives.iter() { + self.remove_directive_name_references(referencers, &directive_reference.name); + } + for (field_name, field) in type_.fields.iter() { + self.field(field_name.clone()) + .remove_references(field, schema, referencers)?; + } + Ok(()) + } + + fn insert_directive_name_references( + &self, + referencers: &mut Referencers, + name: &Name, + ) -> Result<(), FederationError> { + let directive_referencers = referencers.directives.get_mut(name).ok_or_else(|| { + SingleFederationError::Internal { + message: format!( + "Input object type \"{}\"'s directive application \"@{}\" does not refer to an existing directive.", + self, + name, + ), + } + })?; + directive_referencers + .input_object_types + .insert(self.clone()); + Ok(()) + } + + fn remove_directive_name_references(&self, referencers: &mut Referencers, name: &str) { + let Some(directive_referencers) = referencers.directives.get_mut(name) else { + return; + }; + directive_referencers.input_object_types.shift_remove(self); + } +} + +impl Display for InputObjectTypeDefinitionPosition { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.type_name) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub(crate) struct InputObjectFieldDefinitionPosition { + pub(crate) type_name: Name, + pub(crate) field_name: Name, +} + +impl InputObjectFieldDefinitionPosition { + pub(crate) fn parent(&self) -> InputObjectTypeDefinitionPosition { + InputObjectTypeDefinitionPosition { + type_name: self.type_name.clone(), + } + } + + pub(crate) fn get<'schema>( + &self, + schema: &'schema Schema, + ) -> Result<&'schema Component, FederationError> { + let parent = self.parent(); + let type_ = parent.get(schema)?; + + type_.fields.get(&self.field_name).ok_or_else(|| { + SingleFederationError::Internal { + message: format!( + "Input object type \"{}\" has no field \"{}\"", + parent, self.field_name + ), + } + .into() + }) + } + + pub(crate) fn try_get<'schema>( + &self, + schema: &'schema Schema, + ) -> Option<&'schema Component> { + self.get(schema).ok() + } + + fn make_mut<'schema>( + &self, + schema: &'schema mut Schema, + ) -> Result<&'schema mut Component, FederationError> { + let parent = self.parent(); + let type_ = parent.make_mut(schema)?.make_mut(); + + type_.fields.get_mut(&self.field_name).ok_or_else(|| { + SingleFederationError::Internal { + message: format!( + "Input object type \"{}\" has no field \"{}\"", + parent, self.field_name + ), + } + .into() + }) + } + + fn try_make_mut<'schema>( + &self, + schema: &'schema mut Schema, + ) -> Option<&'schema mut Component> { + if self.try_get(schema).is_some() { + self.make_mut(schema).ok() + } else { + None + } + } + + pub(crate) fn insert( + &self, + schema: &mut FederationSchema, + field: Component, + ) -> Result<(), FederationError> { + if self.field_name != field.name { + return Err(SingleFederationError::Internal { + message: format!( + "Input object field \"{}\" given field named \"{}\"", + self, field.name, + ), + } + .into()); + } + if self.try_get(&schema.schema).is_some() { + return Err(SingleFederationError::Internal { + message: format!("Input object field \"{}\" already exists in schema", self), + } + .into()); + } + self.parent() + .make_mut(&mut schema.schema)? + .make_mut() + .fields + .insert(self.field_name.clone(), field); + self.insert_references( + self.get(&schema.schema)?, + &schema.schema, + &mut schema.referencers, + ) + } + + pub(crate) fn remove(&self, schema: &mut FederationSchema) -> Result<(), FederationError> { + let Some(field) = self.try_get(&schema.schema) else { + return Ok(()); + }; + self.remove_references(field, &schema.schema, &mut schema.referencers)?; + self.parent() + .make_mut(&mut schema.schema)? + .make_mut() + .fields + .shift_remove(&self.field_name); + Ok(()) + } + + pub(crate) fn remove_recursive( + &self, + schema: &mut FederationSchema, + ) -> Result<(), FederationError> { + self.remove(schema)?; + let parent = self.parent(); + let Some(type_) = parent.try_get(&schema.schema) else { + return Ok(()); + }; + if type_.fields.is_empty() { + parent.remove_recursive(schema)?; + } + Ok(()) + } + + pub(crate) fn insert_directive( + &self, + schema: &mut FederationSchema, + directive: Node, + ) -> Result<(), FederationError> { + let field = self.make_mut(&mut schema.schema)?; + if field + .directives + .iter() + .any(|other_directive| other_directive.ptr_eq(&directive)) + { + return Err(SingleFederationError::Internal { + message: format!( + "Directive application \"@{}\" already exists on input object field \"{}\"", + directive.name, self, + ), + } + .into()); + } + let name = directive.name.clone(); + field.make_mut().directives.push(directive); + self.insert_directive_name_references(&mut schema.referencers, &name) + } + + pub(crate) fn remove_directive_name(&self, schema: &mut FederationSchema, name: &str) { + let Some(field) = self.try_make_mut(&mut schema.schema) else { + return; + }; + self.remove_directive_name_references(&mut schema.referencers, name); + field + .make_mut() + .directives + .retain(|other_directive| other_directive.name != name); + } + + pub(crate) fn remove_directive( + &self, + schema: &mut FederationSchema, + directive: &Node, + ) { + let Some(field) = self.try_make_mut(&mut schema.schema) else { + return; + }; + if !field.directives.iter().any(|other_directive| { + (other_directive.name == directive.name) && !other_directive.ptr_eq(directive) + }) { + self.remove_directive_name_references(&mut schema.referencers, &directive.name); + } + field + .make_mut() + .directives + .retain(|other_directive| !other_directive.ptr_eq(directive)); + } + + fn insert_references( + &self, + field: &Component, + schema: &Schema, + referencers: &mut Referencers, + ) -> Result<(), FederationError> { + if is_graphql_reserved_name(&self.field_name) { + return Err(SingleFederationError::Internal { + message: format!("Cannot insert reserved input object field \"{}\"", self), + } + .into()); + } + validate_node_directives(field.directives.deref())?; + for directive_reference in field.directives.iter() { + self.insert_directive_name_references(referencers, &directive_reference.name)?; + } + self.insert_type_references(field, schema, referencers) + } + + fn remove_references( + &self, + field: &Component, + schema: &Schema, + referencers: &mut Referencers, + ) -> Result<(), FederationError> { + if is_graphql_reserved_name(&self.field_name) { + return Err(SingleFederationError::Internal { + message: format!("Cannot remove reserved input object field \"{}\"", self), + } + .into()); + } + for directive_reference in field.directives.iter() { + self.remove_directive_name_references(referencers, &directive_reference.name); + } + self.remove_type_references(field, schema, referencers) + } + + fn insert_directive_name_references( + &self, + referencers: &mut Referencers, + name: &Name, + ) -> Result<(), FederationError> { + let directive_referencers = referencers.directives.get_mut(name).ok_or_else(|| { + SingleFederationError::Internal { + message: format!( + "Input object field \"{}\"'s directive application \"@{}\" does not refer to an existing directive.", + self, + name, + ), + } + })?; + directive_referencers + .input_object_fields + .insert(self.clone()); + Ok(()) + } + + fn remove_directive_name_references(&self, referencers: &mut Referencers, name: &str) { + let Some(directive_referencers) = referencers.directives.get_mut(name) else { + return; + }; + directive_referencers.input_object_fields.shift_remove(self); + } + + fn insert_type_references( + &self, + argument: &Node, + schema: &Schema, + referencers: &mut Referencers, + ) -> Result<(), FederationError> { + let input_type_reference = argument.ty.inner_named_type(); + match schema.types.get(input_type_reference) { + Some(ExtendedType::Scalar(_)) => { + let scalar_type_referencers = referencers + .scalar_types + .get_mut(input_type_reference) + .ok_or_else(|| SingleFederationError::Internal { + message: format!( + "Schema missing referencers for type \"{}\"", + input_type_reference + ), + })?; + scalar_type_referencers + .input_object_fields + .insert(self.clone()); + } + Some(ExtendedType::Enum(_)) => { + let enum_type_referencers = referencers + .enum_types + .get_mut(input_type_reference) + .ok_or_else(|| SingleFederationError::Internal { + message: format!( + "Schema missing referencers for type \"{}\"", + input_type_reference + ), + })?; + enum_type_referencers + .input_object_fields + .insert(self.clone()); + } + Some(ExtendedType::InputObject(_)) => { + let input_object_type_referencers = referencers + .input_object_types + .get_mut(input_type_reference) + .ok_or_else(|| SingleFederationError::Internal { + message: format!( + "Schema missing referencers for type \"{}\"", + input_type_reference + ), + })?; + input_object_type_referencers + .input_object_fields + .insert(self.clone()); + } + _ => { + return Err( + SingleFederationError::Internal { + message: format!( + "Input object field \"{}\"'s inner type \"{}\" does not refer to an existing input type.", + self, + input_type_reference.deref(), + ) + }.into() + ); + } + } + Ok(()) + } + + fn remove_type_references( + &self, + field: &Component, + schema: &Schema, + referencers: &mut Referencers, + ) -> Result<(), FederationError> { + let input_type_reference = field.ty.inner_named_type(); + match schema.types.get(input_type_reference) { + Some(ExtendedType::Scalar(_)) => { + let Some(scalar_type_referencers) = + referencers.scalar_types.get_mut(input_type_reference) + else { + return Ok(()); + }; + scalar_type_referencers + .input_object_fields + .shift_remove(self); + } + Some(ExtendedType::Enum(_)) => { + let Some(enum_type_referencers) = + referencers.enum_types.get_mut(input_type_reference) + else { + return Ok(()); + }; + enum_type_referencers.input_object_fields.shift_remove(self); + } + Some(ExtendedType::InputObject(_)) => { + let Some(input_object_type_referencers) = + referencers.input_object_types.get_mut(input_type_reference) + else { + return Ok(()); + }; + input_object_type_referencers + .input_object_fields + .shift_remove(self); + } + _ => { + return Err( + SingleFederationError::Internal { + message: format!( + "Input object field \"{}\"'s inner type \"{}\" does not refer to an existing input type.", + self, + input_type_reference.deref(), + ) + }.into() + ); + } + } + Ok(()) + } +} + +impl Display for InputObjectFieldDefinitionPosition { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}.{}", self.type_name, self.field_name) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub(crate) struct DirectiveDefinitionPosition { + pub(crate) directive_name: Name, +} + +impl DirectiveDefinitionPosition { + pub(crate) fn argument(&self, argument_name: Name) -> DirectiveArgumentDefinitionPosition { + DirectiveArgumentDefinitionPosition { + directive_name: self.directive_name.clone(), + argument_name, + } + } + + pub(crate) fn get<'schema>( + &self, + schema: &'schema Schema, + ) -> Result<&'schema Node, FederationError> { + schema + .directive_definitions + .get(&self.directive_name) + .ok_or_else(|| { + SingleFederationError::Internal { + message: format!("Schema has no directive \"{}\"", self), + } + .into() + }) + } + + pub(crate) fn try_get<'schema>( + &self, + schema: &'schema Schema, + ) -> Option<&'schema Node> { + self.get(schema).ok() + } + + fn make_mut<'schema>( + &self, + schema: &'schema mut Schema, + ) -> Result<&'schema mut Node, FederationError> { + schema + .directive_definitions + .get_mut(&self.directive_name) + .ok_or_else(|| { + SingleFederationError::Internal { + message: format!("Schema has no directive \"{}\"", self), + } + .into() + }) + } + + fn try_make_mut<'schema>( + &self, + schema: &'schema mut Schema, + ) -> Option<&'schema mut Node> { + if self.try_get(schema).is_some() { + self.make_mut(schema).ok() + } else { + None + } + } + + pub(crate) fn pre_insert(&self, schema: &mut FederationSchema) -> Result<(), FederationError> { + if schema + .referencers + .directives + .contains_key(&self.directive_name) + { + // TODO: Allow built-in shadowing instead of ignoring them + if is_graphql_reserved_name(&self.directive_name) + || GRAPHQL_BUILTIN_DIRECTIVE_NAMES.contains(&self.directive_name) + { + return Ok(()); + } + return Err(SingleFederationError::Internal { + message: format!("Directive \"{}\" has already been pre-inserted", self), + } + .into()); + } + schema + .referencers + .directives + .insert(self.directive_name.clone(), Default::default()); + Ok(()) + } + + pub(crate) fn insert( + &self, + schema: &mut FederationSchema, + directive: Node, + ) -> Result<(), FederationError> { + if !schema + .referencers + .directives + .contains_key(&self.directive_name) + { + return Err(SingleFederationError::Internal { + message: format!("Directive \"{}\" has not been pre-inserted", self), + } + .into()); + } + if schema + .schema + .directive_definitions + .contains_key(&self.directive_name) + { + // TODO: Allow built-in shadowing instead of ignoring them + if is_graphql_reserved_name(&self.directive_name) + || GRAPHQL_BUILTIN_DIRECTIVE_NAMES.contains(&self.directive_name) + { + return Ok(()); + } + return Err(SingleFederationError::Internal { + message: format!("Directive \"{}\" already exists in schema", self), + } + .into()); + } + schema + .schema + .directive_definitions + .insert(self.directive_name.clone(), directive); + self.insert_references( + self.get(&schema.schema)?, + &schema.schema, + &mut schema.referencers, + ) + } + + pub(crate) fn remove( + &self, + schema: &mut FederationSchema, + ) -> Result, FederationError> { + let Some(referencers) = self.remove_internal(schema)? else { + return Ok(None); + }; + if let Some(schema_definition) = &referencers.schema { + schema_definition.remove_directive_name(schema, &self.directive_name)?; + } + for type_ in &referencers.scalar_types { + type_.remove_directive_name(schema, &self.directive_name); + } + for type_ in &referencers.object_types { + type_.remove_directive_name(schema, &self.directive_name); + } + for field in &referencers.object_fields { + field.remove_directive_name(schema, &self.directive_name); + } + for argument in &referencers.object_field_arguments { + argument.remove_directive_name(schema, &self.directive_name); + } + for type_ in &referencers.interface_types { + type_.remove_directive_name(schema, &self.directive_name); + } + for field in &referencers.interface_fields { + field.remove_directive_name(schema, &self.directive_name); + } + for argument in &referencers.interface_field_arguments { + argument.remove_directive_name(schema, &self.directive_name); + } + for type_ in &referencers.union_types { + type_.remove_directive_name(schema, &self.directive_name); + } + for type_ in &referencers.enum_types { + type_.remove_directive_name(schema, &self.directive_name); + } + for value in &referencers.enum_values { + value.remove_directive_name(schema, &self.directive_name); + } + for type_ in &referencers.input_object_types { + type_.remove_directive_name(schema, &self.directive_name); + } + for field in &referencers.input_object_fields { + field.remove_directive_name(schema, &self.directive_name); + } + for argument in &referencers.directive_arguments { + argument.remove_directive_name(schema, &self.directive_name); + } + Ok(Some(referencers)) + } + + fn remove_internal( + &self, + schema: &mut FederationSchema, + ) -> Result, FederationError> { + let Some(directive) = self.try_get(&schema.schema) else { + return Ok(None); + }; + self.remove_references(directive, &schema.schema, &mut schema.referencers)?; + schema + .schema + .directive_definitions + .shift_remove(&self.directive_name); + Ok(Some( + schema + .referencers + .directives + .shift_remove(&self.directive_name) + .ok_or_else(|| SingleFederationError::Internal { + message: format!("Schema missing referencers for directive \"{}\"", self), + })?, + )) + } + + fn insert_references( + &self, + directive: &Node, + schema: &Schema, + referencers: &mut Referencers, + ) -> Result<(), FederationError> { + for argument in directive.arguments.iter() { + self.argument(argument.name.clone()).insert_references( + argument, + schema, + referencers, + )?; + } + Ok(()) + } + + fn remove_references( + &self, + directive: &Node, + schema: &Schema, + referencers: &mut Referencers, + ) -> Result<(), FederationError> { + for argument in directive.arguments.iter() { + self.argument(argument.name.clone()).remove_references( + argument, + schema, + referencers, + )?; + } + Ok(()) + } +} + +impl Display for DirectiveDefinitionPosition { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "@{}", self.directive_name) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub(crate) struct DirectiveArgumentDefinitionPosition { + pub(crate) directive_name: Name, + pub(crate) argument_name: Name, +} + +impl DirectiveArgumentDefinitionPosition { + pub(crate) fn parent(&self) -> DirectiveDefinitionPosition { + DirectiveDefinitionPosition { + directive_name: self.directive_name.clone(), + } + } + + pub(crate) fn get<'schema>( + &self, + schema: &'schema Schema, + ) -> Result<&'schema Node, FederationError> { + let parent = self.parent(); + let type_ = parent.get(schema)?; + + type_ + .arguments + .iter() + .find(|a| a.name == self.argument_name) + .ok_or_else(|| { + SingleFederationError::Internal { + message: format!( + "Directive \"{}\" has no argument \"{}\"", + parent, self.argument_name + ), + } + .into() + }) + } + + pub(crate) fn try_get<'schema>( + &self, + schema: &'schema Schema, + ) -> Option<&'schema Node> { + self.get(schema).ok() + } + + fn make_mut<'schema>( + &self, + schema: &'schema mut Schema, + ) -> Result<&'schema mut Node, FederationError> { + let parent = self.parent(); + let type_ = parent.make_mut(schema)?.make_mut(); + + type_ + .arguments + .iter_mut() + .find(|a| a.name == self.argument_name) + .ok_or_else(|| { + SingleFederationError::Internal { + message: format!( + "Directive \"{}\" has no argument \"{}\"", + parent, self.argument_name + ), + } + .into() + }) + } + + fn try_make_mut<'schema>( + &self, + schema: &'schema mut Schema, + ) -> Option<&'schema mut Node> { + if self.try_get(schema).is_some() { + self.make_mut(schema).ok() + } else { + None + } + } + + pub(crate) fn insert( + &self, + schema: &mut FederationSchema, + argument: Node, + ) -> Result<(), FederationError> { + if self.argument_name != argument.name { + return Err(SingleFederationError::Internal { + message: format!( + "Directive argument \"{}\" given argument named \"{}\"", + self, argument.name, + ), + } + .into()); + } + if self.try_get(&schema.schema).is_some() { + // TODO: Handle old spec edge case of arguments with non-unique names + return Err(SingleFederationError::Internal { + message: format!("Directive argument \"{}\" already exists in schema", self,), + } + .into()); + } + self.parent() + .make_mut(&mut schema.schema)? + .make_mut() + .arguments + .push(argument); + self.insert_references( + self.get(&schema.schema)?, + &schema.schema, + &mut schema.referencers, + ) + } + + pub(crate) fn remove(&self, schema: &mut FederationSchema) -> Result<(), FederationError> { + let Some(argument) = self.try_get(&schema.schema) else { + return Ok(()); + }; + self.remove_references(argument, &schema.schema, &mut schema.referencers)?; + self.parent() + .make_mut(&mut schema.schema)? + .make_mut() + .arguments + .retain(|other_argument| other_argument.name != self.argument_name); + Ok(()) + } + + pub(crate) fn insert_directive( + &self, + schema: &mut FederationSchema, + directive: Node, + ) -> Result<(), FederationError> { + let argument = self.make_mut(&mut schema.schema)?; + if argument + .directives + .iter() + .any(|other_directive| other_directive.ptr_eq(&directive)) + { + return Err(SingleFederationError::Internal { + message: format!( + "Directive application \"@{}\" already exists on directive argument \"{}\"", + directive.name, self, + ), + } + .into()); + } + let name = directive.name.clone(); + argument.make_mut().directives.push(directive); + self.insert_directive_name_references(&mut schema.referencers, &name) + } + + pub(crate) fn remove_directive_name(&self, schema: &mut FederationSchema, name: &str) { + let Some(argument) = self.try_make_mut(&mut schema.schema) else { + return; + }; + self.remove_directive_name_references(&mut schema.referencers, name); + argument + .make_mut() + .directives + .retain(|other_directive| other_directive.name != name); + } + + pub(crate) fn remove_directive( + &self, + schema: &mut FederationSchema, + directive: &Node, + ) { + let Some(argument) = self.try_make_mut(&mut schema.schema) else { + return; + }; + if !argument.directives.iter().any(|other_directive| { + (other_directive.name == directive.name) && !other_directive.ptr_eq(directive) + }) { + self.remove_directive_name_references(&mut schema.referencers, &directive.name); + } + argument + .make_mut() + .directives + .retain(|other_directive| !other_directive.ptr_eq(directive)); + } + + fn insert_references( + &self, + argument: &Node, + schema: &Schema, + referencers: &mut Referencers, + ) -> Result<(), FederationError> { + if is_graphql_reserved_name(&self.argument_name) { + return Err(SingleFederationError::Internal { + message: format!("Cannot insert reserved directive argument \"{}\"", self), + } + .into()); + } + validate_node_directives(argument.directives.deref())?; + for directive_reference in argument.directives.iter() { + self.insert_directive_name_references(referencers, &directive_reference.name)?; + } + self.insert_type_references(argument, schema, referencers) + } + + fn remove_references( + &self, + argument: &Node, + schema: &Schema, + referencers: &mut Referencers, + ) -> Result<(), FederationError> { + if is_graphql_reserved_name(&self.argument_name) { + return Err(SingleFederationError::Internal { + message: format!("Cannot remove reserved directive argument \"{}\"", self), + } + .into()); + } + for directive_reference in argument.directives.iter() { + self.remove_directive_name_references(referencers, &directive_reference.name); + } + self.remove_type_references(argument, schema, referencers) + } + + fn insert_directive_name_references( + &self, + referencers: &mut Referencers, + name: &Name, + ) -> Result<(), FederationError> { + let directive_referencers = referencers.directives.get_mut(name).ok_or_else(|| { + SingleFederationError::Internal { + message: format!( + "Directive argument \"{}\"'s directive application \"@{}\" does not refer to an existing directive.", + self, + name, + ), + } + })?; + directive_referencers + .directive_arguments + .insert(self.clone()); + Ok(()) + } + + fn remove_directive_name_references(&self, referencers: &mut Referencers, name: &str) { + let Some(directive_referencers) = referencers.directives.get_mut(name) else { + return; + }; + directive_referencers.directive_arguments.shift_remove(self); + } + + fn insert_type_references( + &self, + argument: &Node, + schema: &Schema, + referencers: &mut Referencers, + ) -> Result<(), FederationError> { + let input_type_reference = argument.ty.inner_named_type(); + match schema.types.get(input_type_reference) { + Some(ExtendedType::Scalar(_)) => { + let scalar_type_referencers = referencers + .scalar_types + .get_mut(input_type_reference) + .ok_or_else(|| SingleFederationError::Internal { + message: format!( + "Schema missing referencers for type \"{}\"", + input_type_reference + ), + })?; + scalar_type_referencers + .directive_arguments + .insert(self.clone()); + } + Some(ExtendedType::Enum(_)) => { + let enum_type_referencers = referencers + .enum_types + .get_mut(input_type_reference) + .ok_or_else(|| SingleFederationError::Internal { + message: format!( + "Schema missing referencers for type \"{}\"", + input_type_reference + ), + })?; + enum_type_referencers + .directive_arguments + .insert(self.clone()); + } + Some(ExtendedType::InputObject(_)) => { + let input_object_type_referencers = referencers + .input_object_types + .get_mut(input_type_reference) + .ok_or_else(|| SingleFederationError::Internal { + message: format!( + "Schema missing referencers for type \"{}\"", + input_type_reference + ), + })?; + input_object_type_referencers + .directive_arguments + .insert(self.clone()); + } + _ => { + return Err( + SingleFederationError::Internal { + message: format!( + "Directive argument \"{}\"'s inner type \"{}\" does not refer to an existing input type.", + self, + input_type_reference.deref(), + ) + }.into() + ); + } + } + Ok(()) + } + + fn remove_type_references( + &self, + argument: &Node, + schema: &Schema, + referencers: &mut Referencers, + ) -> Result<(), FederationError> { + let input_type_reference = argument.ty.inner_named_type(); + match schema.types.get(input_type_reference) { + Some(ExtendedType::Scalar(_)) => { + let Some(scalar_type_referencers) = + referencers.scalar_types.get_mut(input_type_reference) + else { + return Ok(()); + }; + scalar_type_referencers + .directive_arguments + .shift_remove(self); + } + Some(ExtendedType::Enum(_)) => { + let Some(enum_type_referencers) = + referencers.enum_types.get_mut(input_type_reference) + else { + return Ok(()); + }; + enum_type_referencers.directive_arguments.shift_remove(self); + } + Some(ExtendedType::InputObject(_)) => { + let Some(input_object_type_referencers) = + referencers.input_object_types.get_mut(input_type_reference) + else { + return Ok(()); + }; + input_object_type_referencers + .directive_arguments + .shift_remove(self); + } + _ => { + return Err( + SingleFederationError::Internal { + message: format!( + "Directive argument \"{}\"'s inner type \"{}\" does not refer to an existing input type.", + self, + input_type_reference.deref(), + ) + }.into() + ); + } + } + Ok(()) + } +} + +impl Display for DirectiveArgumentDefinitionPosition { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "@{}({}:)", self.directive_name, self.argument_name) + } +} + +pub(crate) fn is_graphql_reserved_name(name: &str) -> bool { + name.starts_with("__") +} + +lazy_static! { + static ref GRAPHQL_BUILTIN_SCALAR_NAMES: IndexSet = { + IndexSet::from([ + name!("Int"), + name!("Float"), + name!("String"), + name!("Boolean"), + name!("ID"), + ]) + }; + static ref GRAPHQL_BUILTIN_DIRECTIVE_NAMES: IndexSet = { + IndexSet::from([ + name!("include"), + name!("skip"), + name!("deprecated"), + name!("specifiedBy"), + name!("defer"), + ]) + }; + // This is static so that UnionTypenameFieldDefinitionPosition.field_name() can return `&Name`, + // like the other field_name() methods in this file. + pub(crate) static ref INTROSPECTION_TYPENAME_FIELD_NAME: Name = name!("__typename"); +} + +fn validate_component_directives( + directives: &[Component], +) -> Result<(), FederationError> { + for directive in directives.iter() { + if directives + .iter() + .filter(|other_directive| other_directive.ptr_eq(directive)) + .count() + > 1 + { + return Err(SingleFederationError::Internal { + message: format!( + "Directive application \"@{}\" is duplicated on schema element", + directive.name, + ), + } + .into()); + } + } + Ok(()) +} + +fn validate_node_directives(directives: &[Node]) -> Result<(), FederationError> { + for directive in directives.iter() { + if directives + .iter() + .filter(|other_directive| other_directive.ptr_eq(directive)) + .count() + > 1 + { + return Err(SingleFederationError::Internal { + message: format!( + "Directive application \"@{}\" is duplicated on schema element", + directive.name, + ), + } + .into()); + } + } + Ok(()) +} + +fn validate_arguments(arguments: &[Node]) -> Result<(), FederationError> { + for argument in arguments.iter() { + if arguments + .iter() + .filter(|other_argument| other_argument.name == argument.name) + .count() + > 1 + { + return Err(SingleFederationError::Internal { + message: format!( + "Argument \"{}\" is duplicated on schema element", + argument.name, + ), + } + .into()); + } + } + Ok(()) +} + +impl FederationSchema { + /// Note that the input schema must be partially valid, in that: + /// 1. All schema element references must point to an existing schema element of the appropriate + /// kind (e.g. object type fields must return an existing output type). + /// 2. If the schema uses the core/link spec, then usages of the @core/@link directive must be + /// valid. + /// The input schema may be otherwise invalid GraphQL (e.g. it may not contain a Query type). If + /// you want a ValidFederationSchema, use ValidFederationSchema::new() instead. + pub(crate) fn new(schema: Schema) -> Result { + let metadata = links_metadata(&schema)?; + let mut referencers: Referencers = Default::default(); + + // Shallow pass to populate referencers for types/directives. + for (type_name, type_) in schema.types.iter() { + match type_ { + ExtendedType::Scalar(_) => { + referencers + .scalar_types + .insert(type_name.clone(), Default::default()); + } + ExtendedType::Object(_) => { + referencers + .object_types + .insert(type_name.clone(), Default::default()); + } + ExtendedType::Interface(_) => { + referencers + .interface_types + .insert(type_name.clone(), Default::default()); + } + ExtendedType::Union(_) => { + referencers + .union_types + .insert(type_name.clone(), Default::default()); + } + ExtendedType::Enum(_) => { + referencers + .enum_types + .insert(type_name.clone(), Default::default()); + } + ExtendedType::InputObject(_) => { + referencers + .input_object_types + .insert(type_name.clone(), Default::default()); + } + } + } + for directive_name in schema.directive_definitions.keys() { + referencers + .directives + .insert(directive_name.clone(), Default::default()); + } + + // Deep pass to find references. + SchemaDefinitionPosition.insert_references( + &schema.schema_definition, + &schema, + &mut referencers, + )?; + for (type_name, type_) in schema.types.iter() { + match type_ { + ExtendedType::Scalar(type_) => { + ScalarTypeDefinitionPosition { + type_name: type_name.clone(), + } + .insert_references(type_, &mut referencers)?; + } + ExtendedType::Object(type_) => { + ObjectTypeDefinitionPosition { + type_name: type_name.clone(), + } + .insert_references(type_, &schema, &mut referencers)?; + } + ExtendedType::Interface(type_) => { + InterfaceTypeDefinitionPosition { + type_name: type_name.clone(), + } + .insert_references(type_, &schema, &mut referencers)?; + } + ExtendedType::Union(type_) => { + UnionTypeDefinitionPosition { + type_name: type_name.clone(), + } + .insert_references(type_, &mut referencers)?; + } + ExtendedType::Enum(type_) => { + EnumTypeDefinitionPosition { + type_name: type_name.clone(), + } + .insert_references(type_, &mut referencers)?; + } + ExtendedType::InputObject(type_) => { + InputObjectTypeDefinitionPosition { + type_name: type_name.clone(), + } + .insert_references(type_, &schema, &mut referencers)?; + } + } + } + for (directive_name, directive) in schema.directive_definitions.iter() { + DirectiveDefinitionPosition { + directive_name: directive_name.clone(), + } + .insert_references(directive, &schema, &mut referencers)?; + } + + Ok(FederationSchema { + schema, + referencers, + links_metadata: metadata.map(Box::new), + subgraph_metadata: None, + }) + } +} diff --git a/apollo-federation/src/schema/referencer.rs b/apollo-federation/src/schema/referencer.rs new file mode 100644 index 0000000000..a0855eec8e --- /dev/null +++ b/apollo-federation/src/schema/referencer.rs @@ -0,0 +1,197 @@ +use apollo_compiler::schema::Name; +use indexmap::IndexMap; +use indexmap::IndexSet; + +use crate::error::FederationError; +use crate::error::SingleFederationError; +use crate::schema::position::DirectiveArgumentDefinitionPosition; +use crate::schema::position::EnumTypeDefinitionPosition; +use crate::schema::position::EnumValueDefinitionPosition; +use crate::schema::position::InputObjectFieldDefinitionPosition; +use crate::schema::position::InputObjectTypeDefinitionPosition; +use crate::schema::position::InterfaceFieldArgumentDefinitionPosition; +use crate::schema::position::InterfaceFieldDefinitionPosition; +use crate::schema::position::InterfaceTypeDefinitionPosition; +use crate::schema::position::ObjectFieldArgumentDefinitionPosition; +use crate::schema::position::ObjectFieldDefinitionPosition; +use crate::schema::position::ObjectTypeDefinitionPosition; +use crate::schema::position::ScalarTypeDefinitionPosition; +use crate::schema::position::SchemaDefinitionPosition; +use crate::schema::position::SchemaRootDefinitionPosition; +use crate::schema::position::UnionTypeDefinitionPosition; +use crate::schema::position::UnionTypenameFieldDefinitionPosition; + +#[derive(Debug, Clone, Default)] +pub(crate) struct Referencers { + pub(crate) scalar_types: IndexMap, + pub(crate) object_types: IndexMap, + pub(crate) interface_types: IndexMap, + pub(crate) union_types: IndexMap, + pub(crate) enum_types: IndexMap, + pub(crate) input_object_types: IndexMap, + pub(crate) directives: IndexMap, +} + +impl Referencers { + pub(crate) fn contains_type_name(&self, name: &str) -> bool { + self.scalar_types.contains_key(name) + || self.object_types.contains_key(name) + || self.interface_types.contains_key(name) + || self.union_types.contains_key(name) + || self.enum_types.contains_key(name) + || self.input_object_types.contains_key(name) + } + + pub(crate) fn get_scalar_type( + &self, + name: &str, + ) -> Result<&ScalarTypeReferencers, FederationError> { + self.scalar_types.get(name).ok_or_else(|| { + SingleFederationError::Internal { + message: "Scalar type referencers unexpectedly missing type".to_owned(), + } + .into() + }) + } + + pub(crate) fn get_object_type( + &self, + name: &str, + ) -> Result<&ObjectTypeReferencers, FederationError> { + self.object_types.get(name).ok_or_else(|| { + SingleFederationError::Internal { + message: "Object type referencers unexpectedly missing type".to_owned(), + } + .into() + }) + } + + pub(crate) fn get_interface_type( + &self, + name: &str, + ) -> Result<&InterfaceTypeReferencers, FederationError> { + self.interface_types.get(name).ok_or_else(|| { + SingleFederationError::Internal { + message: "Interface type referencers unexpectedly missing type".to_owned(), + } + .into() + }) + } + + pub(crate) fn get_union_type( + &self, + name: &str, + ) -> Result<&UnionTypeReferencers, FederationError> { + self.union_types.get(name).ok_or_else(|| { + SingleFederationError::Internal { + message: "Union type referencers unexpectedly missing type".to_owned(), + } + .into() + }) + } + + pub(crate) fn get_enum_type( + &self, + name: &str, + ) -> Result<&EnumTypeReferencers, FederationError> { + self.enum_types.get(name).ok_or_else(|| { + SingleFederationError::Internal { + message: "Enum type referencers unexpectedly missing type".to_owned(), + } + .into() + }) + } + + pub(crate) fn get_input_object_type( + &self, + name: &str, + ) -> Result<&InputObjectTypeReferencers, FederationError> { + self.input_object_types.get(name).ok_or_else(|| { + SingleFederationError::Internal { + message: "Input object type referencers unexpectedly missing type".to_owned(), + } + .into() + }) + } + + pub(crate) fn get_directive( + &self, + name: &str, + ) -> Result<&DirectiveReferencers, FederationError> { + self.directives.get(name).ok_or_else(|| { + SingleFederationError::Internal { + message: "Directive referencers unexpectedly missing directive".to_owned(), + } + .into() + }) + } +} + +#[derive(Debug, Clone, Default)] +pub(crate) struct ScalarTypeReferencers { + pub(crate) object_fields: IndexSet, + pub(crate) object_field_arguments: IndexSet, + pub(crate) interface_fields: IndexSet, + pub(crate) interface_field_arguments: IndexSet, + pub(crate) union_fields: IndexSet, + pub(crate) input_object_fields: IndexSet, + pub(crate) directive_arguments: IndexSet, +} + +#[derive(Debug, Clone, Default)] +pub(crate) struct ObjectTypeReferencers { + pub(crate) schema_roots: IndexSet, + pub(crate) object_fields: IndexSet, + pub(crate) interface_fields: IndexSet, + pub(crate) union_types: IndexSet, +} + +#[derive(Debug, Clone, Default)] +pub(crate) struct InterfaceTypeReferencers { + pub(crate) object_types: IndexSet, + pub(crate) object_fields: IndexSet, + pub(crate) interface_types: IndexSet, + pub(crate) interface_fields: IndexSet, +} + +#[derive(Debug, Clone, Default)] +pub(crate) struct UnionTypeReferencers { + pub(crate) object_fields: IndexSet, + pub(crate) interface_fields: IndexSet, +} + +#[derive(Debug, Clone, Default)] +pub(crate) struct EnumTypeReferencers { + pub(crate) object_fields: IndexSet, + pub(crate) object_field_arguments: IndexSet, + pub(crate) interface_fields: IndexSet, + pub(crate) interface_field_arguments: IndexSet, + pub(crate) input_object_fields: IndexSet, + pub(crate) directive_arguments: IndexSet, +} + +#[derive(Debug, Clone, Default)] +pub(crate) struct InputObjectTypeReferencers { + pub(crate) object_field_arguments: IndexSet, + pub(crate) interface_field_arguments: IndexSet, + pub(crate) input_object_fields: IndexSet, + pub(crate) directive_arguments: IndexSet, +} + +#[derive(Debug, Clone, Default)] +pub(crate) struct DirectiveReferencers { + pub(crate) schema: Option, + pub(crate) scalar_types: IndexSet, + pub(crate) object_types: IndexSet, + pub(crate) object_fields: IndexSet, + pub(crate) object_field_arguments: IndexSet, + pub(crate) interface_types: IndexSet, + pub(crate) interface_fields: IndexSet, + pub(crate) interface_field_arguments: IndexSet, + pub(crate) union_types: IndexSet, + pub(crate) enum_types: IndexSet, + pub(crate) enum_values: IndexSet, + pub(crate) input_object_types: IndexSet, + pub(crate) input_object_fields: IndexSet, + pub(crate) directive_arguments: IndexSet, +} diff --git a/apollo-federation/src/schema/subgraph_metadata.rs b/apollo-federation/src/schema/subgraph_metadata.rs new file mode 100644 index 0000000000..826b44f974 --- /dev/null +++ b/apollo-federation/src/schema/subgraph_metadata.rs @@ -0,0 +1,312 @@ +use apollo_compiler::validation::Valid; +use apollo_compiler::Schema; +use indexmap::IndexSet; + +use crate::error::FederationError; +use crate::link::federation_spec_definition::FederationSpecDefinition; +use crate::link::spec::Version; +use crate::link::spec_definition::SpecDefinition; +use crate::query_plan::operation::Selection; +use crate::query_plan::operation::SelectionSet; +use crate::schema::field_set::add_interface_field_implementations; +use crate::schema::field_set::collect_target_fields_from_field_set; +use crate::schema::position::CompositeTypeDefinitionPosition; +use crate::schema::position::FieldDefinitionPosition; +use crate::schema::position::ObjectOrInterfaceFieldDefinitionPosition; +use crate::schema::position::ObjectOrInterfaceTypeDefinitionPosition; +use crate::schema::FederationSchema; + +fn unwrap_schema(fed_schema: &Valid) -> &Valid { + // Okay to assume valid because `fed_schema` is known to be valid. + Valid::assume_valid_ref(fed_schema.schema()) +} + +// PORT_NOTE: The JS codebase called this `FederationMetadata`, but this naming didn't make it +// apparent that this was just subgraph schema metadata, so we've renamed it accordingly. +#[derive(Debug, Clone)] +pub(crate) struct SubgraphMetadata { + federation_spec_definition: &'static FederationSpecDefinition, + is_fed2: bool, + external_metadata: ExternalMetadata, +} + +impl SubgraphMetadata { + pub(super) fn new( + schema: &Valid, + federation_spec_definition: &'static FederationSpecDefinition, + ) -> Result { + let is_fed2 = federation_spec_definition + .version() + .satisfies(&Version { major: 2, minor: 0 }); + let external_metadata = ExternalMetadata::new(schema, federation_spec_definition)?; + Ok(Self { + federation_spec_definition, + is_fed2, + external_metadata, + }) + } + + pub(crate) fn federation_spec_definition(&self) -> &'static FederationSpecDefinition { + self.federation_spec_definition + } + + pub(crate) fn is_fed2(&self) -> bool { + self.is_fed2 + } + + pub(crate) fn external_metadata(&self) -> &ExternalMetadata { + &self.external_metadata + } +} + +// PORT_NOTE: The JS codebase called this `ExternalTester`, but this naming didn't make it +// apparent that this was just @external-related subgraph metadata, so we've renamed it accordingly. +// Also note the field "externalFieldsOnType" was renamed to "fields_on_external_types", as it's +// more accurate. +#[derive(Debug, Clone)] +pub(crate) struct ExternalMetadata { + /// All fields with an `@external` directive. + external_fields: IndexSet, + /// Fields with an `@external` directive that can't actually be external due to also being + /// referenced in a `@key` directive. + fake_external_fields: IndexSet, + /// Fields that are only sometimes external, and sometimes reachable due to being included + /// in a `@provides` directive. + provided_fields: IndexSet, + /// Fields that are external because their parent type has an `@external` directive. + fields_on_external_types: IndexSet, +} + +impl ExternalMetadata { + fn new( + schema: &Valid, + federation_spec_definition: &'static FederationSpecDefinition, + ) -> Result { + let external_fields = Self::collect_external_fields(federation_spec_definition, schema)?; + let fake_external_fields = + Self::collect_fake_externals(federation_spec_definition, schema)?; + let provided_fields = Self::collect_provided_fields(federation_spec_definition, schema)?; + // We do not collect @external on types for Fed 1 schemas since those will be discarded by + // the schema upgrader. The schema upgrader, through calls to `is_external()`, relies on the + // populated `fields_on_external_types` set to inform when @shareable should be + // automatically added. In the Fed 1 case, if the set is populated then @shareable won't be + // added in places where it should be. + let is_fed2 = federation_spec_definition + .version() + .satisfies(&Version { major: 2, minor: 0 }); + let fields_on_external_types = if is_fed2 { + Self::collect_fields_on_external_types(federation_spec_definition, schema)? + } else { + Default::default() + }; + + Ok(Self { + external_fields, + fake_external_fields, + provided_fields, + fields_on_external_types, + }) + } + + fn collect_external_fields( + federation_spec_definition: &'static FederationSpecDefinition, + schema: &Valid, + ) -> Result, FederationError> { + let external_directive_definition = federation_spec_definition + .external_directive_definition(schema)? + .clone(); + + let external_directive_referencers = schema + .referencers + .get_directive(&external_directive_definition.name)?; + + let mut external_fields = IndexSet::new(); + + external_fields.extend( + external_directive_referencers + .object_fields + .iter() + .map(|field| field.clone().into()), + ); + + external_fields.extend( + external_directive_referencers + .interface_fields + .iter() + .map(|field| field.clone().into()), + ); + + Ok(external_fields) + } + + fn collect_fake_externals( + federation_spec_definition: &'static FederationSpecDefinition, + schema: &Valid, + ) -> Result, FederationError> { + let mut fake_external_fields = IndexSet::new(); + let extends_directive_definition = + federation_spec_definition.extends_directive_definition(schema)?; + let key_directive_definition = + federation_spec_definition.key_directive_definition(schema)?; + let key_directive_referencers = schema + .referencers + .get_directive(&key_directive_definition.name)?; + let mut key_type_positions: Vec = vec![]; + for object_type_position in &key_directive_referencers.object_types { + key_type_positions.push(object_type_position.clone().into()); + } + for interface_type_position in &key_directive_referencers.interface_types { + key_type_positions.push(interface_type_position.clone().into()); + } + for type_position in key_type_positions { + let directives = match &type_position { + ObjectOrInterfaceTypeDefinitionPosition::Object(pos) => { + &pos.get(schema.schema())?.directives + } + ObjectOrInterfaceTypeDefinitionPosition::Interface(pos) => { + &pos.get(schema.schema())?.directives + } + }; + let has_extends_directive = directives.has(&extends_directive_definition.name); + for key_directive_application in directives.get_all(&key_directive_definition.name) { + // PORT_NOTE: The JS codebase treats the "extend" GraphQL keyword as applying to + // only the extension it's on, while it treats the "@extends" directive as applying + // to all definitions/extensions in the subgraph. We accordingly do the same. + if has_extends_directive + || key_directive_application.origin.extension_id().is_some() + { + let key_directive_arguments = federation_spec_definition + .key_directive_arguments(key_directive_application)?; + fake_external_fields.extend(collect_target_fields_from_field_set( + unwrap_schema(schema), + type_position.type_name().clone(), + key_directive_arguments.fields, + )?); + } + } + } + Ok(fake_external_fields) + } + + fn collect_provided_fields( + federation_spec_definition: &'static FederationSpecDefinition, + schema: &Valid, + ) -> Result, FederationError> { + let mut provided_fields = IndexSet::new(); + let provides_directive_definition = + federation_spec_definition.provides_directive_definition(schema)?; + let provides_directive_referencers = schema + .referencers + .get_directive(&provides_directive_definition.name)?; + let mut provides_field_positions: Vec = vec![]; + for object_field_position in &provides_directive_referencers.object_fields { + provides_field_positions.push(object_field_position.clone().into()); + } + for interface_field_position in &provides_directive_referencers.interface_fields { + provides_field_positions.push(interface_field_position.clone().into()); + } + for field_position in provides_field_positions { + let field = field_position.get(schema.schema())?; + let field_type_position: CompositeTypeDefinitionPosition = schema + .get_type(field.ty.inner_named_type().clone())? + .try_into()?; + for provides_directive_application in field + .directives + .get_all(&provides_directive_definition.name) + { + let provides_directive_arguments = federation_spec_definition + .provides_directive_arguments(provides_directive_application)?; + provided_fields.extend(add_interface_field_implementations( + collect_target_fields_from_field_set( + unwrap_schema(schema), + field_type_position.type_name().clone(), + provides_directive_arguments.fields, + )?, + schema, + )?); + } + } + Ok(provided_fields) + } + + fn collect_fields_on_external_types( + federation_spec_definition: &'static FederationSpecDefinition, + schema: &Valid, + ) -> Result, FederationError> { + let external_directive_definition = federation_spec_definition + .external_directive_definition(schema)? + .clone(); + + let external_directive_referencers = schema + .referencers + .get_directive(&external_directive_definition.name)?; + + let mut fields_on_external_types = IndexSet::new(); + for object_type_position in &external_directive_referencers.object_types { + let object_type = object_type_position.get(schema.schema())?; + // PORT_NOTE: The JS codebase does not differentiate fields at a definition/extension + // level here, and we accordingly do the same. I.e., if a type is marked @external for + // one definition/extension in a subgraph, then it is considered to be marked @external + // for all definitions/extensions in that subgraph. + for field_name in object_type.fields.keys() { + fields_on_external_types + .insert(object_type_position.field(field_name.clone()).into()); + } + } + Ok(fields_on_external_types) + } + + pub(crate) fn is_external( + &self, + field_definition_position: &FieldDefinitionPosition, + ) -> Result { + Ok((self.external_fields.contains(field_definition_position) + || self + .fields_on_external_types + .contains(field_definition_position)) + && !self.is_fake_external(field_definition_position)) + } + + pub(crate) fn is_fake_external( + &self, + field_definition_position: &FieldDefinitionPosition, + ) -> bool { + self.fake_external_fields + .contains(field_definition_position) + } + + pub(crate) fn selects_any_external_field( + &self, + selection_set: &SelectionSet, + ) -> Result { + for selection in selection_set.selections.values() { + if let Selection::Field(field_selection) = selection { + if self.is_external(&field_selection.field.data().field_position)? { + return Ok(true); + } + } + if let Some(selection_set) = selection.selection_set()? { + if self.selects_any_external_field(selection_set)? { + return Ok(true); + } + } + } + Ok(false) + } + + pub(crate) fn is_partially_external( + &self, + field_definition_position: &FieldDefinitionPosition, + ) -> Result { + Ok(self.is_external(field_definition_position)? + && self.provided_fields.contains(field_definition_position)) + } + + pub(crate) fn is_fully_external( + &self, + field_definition_position: &FieldDefinitionPosition, + ) -> Result { + Ok(self.is_external(field_definition_position)? + && !self.provided_fields.contains(field_definition_position)) + } +} diff --git a/apollo-federation/src/schema/type_and_directive_specification.rs b/apollo-federation/src/schema/type_and_directive_specification.rs new file mode 100644 index 0000000000..3cae98f13d --- /dev/null +++ b/apollo-federation/src/schema/type_and_directive_specification.rs @@ -0,0 +1,880 @@ +use apollo_compiler::ast::DirectiveLocation; +use apollo_compiler::ast::FieldDefinition; +use apollo_compiler::ast::Value; +use apollo_compiler::schema::Component; +use apollo_compiler::schema::ComponentName; +use apollo_compiler::schema::DirectiveDefinition; +use apollo_compiler::schema::EnumType; +use apollo_compiler::schema::EnumValueDefinition; +use apollo_compiler::schema::ExtendedType; +use apollo_compiler::schema::InputValueDefinition; +use apollo_compiler::schema::Name; +use apollo_compiler::schema::ObjectType; +use apollo_compiler::schema::ScalarType; +use apollo_compiler::schema::Type; +use apollo_compiler::schema::UnionType; +use apollo_compiler::Node; +use indexmap::IndexMap; +use indexmap::IndexSet; + +use crate::error::FederationError; +use crate::error::MultipleFederationErrors; +use crate::error::SingleFederationError; +use crate::link::spec::Version; +use crate::link::spec_definition::SpecDefinition; +use crate::schema::argument_composition_strategies::ArgumentCompositionStrategy; +use crate::schema::position::DirectiveDefinitionPosition; +use crate::schema::position::EnumTypeDefinitionPosition; +use crate::schema::position::ObjectTypeDefinitionPosition; +use crate::schema::position::ScalarTypeDefinitionPosition; +use crate::schema::position::TypeDefinitionPosition; +use crate::schema::position::UnionTypeDefinitionPosition; +use crate::schema::FederationSchema; + +////////////////////////////////////////////////////////////////////////////// +// Field and Argument Specifications + +/// Schema-dependent argument specification +#[derive(Clone)] +pub(crate) struct ArgumentSpecification { + pub name: Name, + // PORT_NOTE: In TS, get_type returns `InputType`. + pub get_type: fn(schema: &FederationSchema) -> Result, + pub default_value: Option, +} + +/// The resolved version of `ArgumentSpecification` +pub(crate) struct ResolvedArgumentSpecification { + pub name: Name, + pub ty: Type, + default_value: Option, +} + +impl From<&ResolvedArgumentSpecification> for InputValueDefinition { + fn from(arg_spec: &ResolvedArgumentSpecification) -> Self { + InputValueDefinition { + description: None, + name: arg_spec.name.clone(), + ty: Node::new(arg_spec.ty.clone()), + default_value: arg_spec + .default_value + .as_ref() + .map(|v| Node::new(v.clone())), + directives: Default::default(), + } + } +} + +pub(crate) struct FieldSpecification { + pub name: Name, + pub ty: Type, + pub arguments: Vec, +} + +impl From<&FieldSpecification> for FieldDefinition { + fn from(field_spec: &FieldSpecification) -> Self { + FieldDefinition { + description: None, + name: field_spec.name.clone(), + arguments: field_spec + .arguments + .iter() + .map(|arg| Node::new(arg.into())) + .collect(), + ty: field_spec.ty.clone(), + directives: Default::default(), + } + } +} + +////////////////////////////////////////////////////////////////////////////// +// Type Specifications + +pub(crate) trait TypeAndDirectiveSpecification { + // PORT_NOTE: The JS version takes additional optional arguments `feature` and `asBuiltIn`. + fn check_or_add(&self, schema: &mut FederationSchema) -> Result<(), FederationError>; +} + +pub(crate) struct ScalarTypeSpecification { + pub name: Name, // Type's name +} + +impl TypeAndDirectiveSpecification for ScalarTypeSpecification { + fn check_or_add(&self, schema: &mut FederationSchema) -> Result<(), FederationError> { + let existing = schema.try_get_type(self.name.clone()); + if let Some(existing) = existing { + // Ignore redundant type specifications if they are are both scalar types. + return ensure_expected_type_kind(TypeKind::Scalar, &existing); + } + + let type_pos = ScalarTypeDefinitionPosition { + type_name: self.name.clone(), + }; + type_pos.pre_insert(schema)?; + type_pos.insert( + schema, + Node::new(ScalarType { + description: None, + name: type_pos.type_name.clone(), + directives: Default::default(), + }), + ) + } +} + +pub(crate) struct ObjectTypeSpecification { + pub name: Name, + pub fields: fn(&FederationSchema) -> Vec, +} + +impl TypeAndDirectiveSpecification for ObjectTypeSpecification { + fn check_or_add(&self, schema: &mut FederationSchema) -> Result<(), FederationError> { + let field_specs = (self.fields)(schema); + let existing = schema.try_get_type(self.name.clone()); + if let Some(existing) = existing { + // ensure existing definition is an object type + ensure_expected_type_kind(TypeKind::Object, &existing)?; + let existing_type = existing.get(schema.schema())?; + let ExtendedType::Object(existing_obj_type) = existing_type else { + return Err(FederationError::internal(format!( + "Expected ExtendedType::Object but got {}", + TypeKind::from(existing_type) + ))); + }; + + // ensure all expected fields are present in the existing object type + let errors = ensure_same_fields(existing_obj_type, &field_specs, schema); + return MultipleFederationErrors::from_iter(errors).into_result(); + } + + let mut field_map = IndexMap::new(); + for ref field_spec in field_specs { + let field_def: FieldDefinition = field_spec.into(); + field_map.insert(field_spec.name.clone(), Component::new(field_def)); + } + + let type_pos = ObjectTypeDefinitionPosition { + type_name: self.name.clone(), + }; + type_pos.pre_insert(schema)?; + type_pos.insert( + schema, + Node::new(ObjectType { + description: None, + name: type_pos.type_name.clone(), + implements_interfaces: Default::default(), + directives: Default::default(), + fields: field_map, + }), + ) + } +} + +pub(crate) struct UnionTypeSpecification +where + F: Fn(&FederationSchema) -> IndexSet, +{ + pub name: Name, + pub members: F, +} + +impl TypeAndDirectiveSpecification for UnionTypeSpecification +where + F: Fn(&FederationSchema) -> IndexSet, +{ + fn check_or_add(&self, schema: &mut FederationSchema) -> Result<(), FederationError> { + let members = (self.members)(schema); + let existing = schema.try_get_type(self.name.clone()); + + // ensure new union has at least one member + if members.is_empty() { + if existing.is_some() { + let union_type_name = &self.name; + return Err(SingleFederationError::TypeDefinitionInvalid { + message: format!("Invalid definition of type {union_type_name}: expected the union type to not exist/have no members but it is defined.") + }.into()); + } + return Ok(()); // silently ignore empty unions + } + + // ensure new union has the same members as the existing union + if let Some(existing) = existing { + ensure_expected_type_kind(TypeKind::Union, &existing)?; + let existing_type = existing.get(schema.schema())?; + let ExtendedType::Union(existing_union_type) = existing_type else { + return Err(FederationError::internal(format!( + "Expected ExtendedType::Object but got {}", + TypeKind::from(existing_type) + ))); + }; + if existing_union_type.members != members { + let union_type_name = &self.name; + let expected_member_names: Vec = existing_union_type + .members + .iter() + .map(|name| name.to_string()) + .collect(); + let actual_member_names: Vec = + members.iter().map(|name| name.to_string()).collect(); + return Err(SingleFederationError::TypeDefinitionInvalid { + message: format!("Invalid definition of type {union_type_name}: expected members [{}] but found [{}]", + expected_member_names.join(", "), actual_member_names.join(", ")) + }.into()); + } + return Ok(()); + } + + let type_pos = UnionTypeDefinitionPosition { + type_name: self.name.clone(), + }; + type_pos.pre_insert(schema)?; + type_pos.insert( + schema, + Node::new(UnionType { + description: None, + name: type_pos.type_name.clone(), + directives: Default::default(), + members, + }), + ) + } +} + +pub(crate) struct EnumValueSpecification { + pub name: Name, + pub description: Option, +} + +pub(crate) struct EnumTypeSpecification { + pub name: Name, + pub values: Vec, +} + +impl TypeAndDirectiveSpecification for EnumTypeSpecification { + fn check_or_add(&self, schema: &mut FederationSchema) -> Result<(), FederationError> { + let existing = schema.try_get_type(self.name.clone()); + if let Some(existing) = existing { + ensure_expected_type_kind(TypeKind::Enum, &existing)?; + let existing_type = existing.get(schema.schema())?; + let ExtendedType::Enum(existing_type) = existing_type else { + return Err(FederationError::internal(format!( + "Expected ExtendedType::Union but got {}", + TypeKind::from(existing_type) + ))); + }; + + let existing_value_set: IndexSet = existing_type + .values + .iter() + .map(|val| val.0.clone()) + .collect(); + let actual_value_set: IndexSet = + self.values.iter().map(|val| val.name.clone()).collect(); + if existing_value_set != actual_value_set { + let enum_type_name = &self.name; + let expected_value_names: Vec = existing_value_set + .iter() + .map(|name| name.to_string()) + .collect(); + let actual_value_names: Vec = actual_value_set + .iter() + .map(|name| name.to_string()) + .collect(); + return Err(SingleFederationError::TypeDefinitionInvalid { + message: format!("Invalid definition of type {enum_type_name}: expected values [{}] but found [{}].", + expected_value_names.join(", "), actual_value_names.join(", ")) + }.into()); + } + return Ok(()); + } + + let type_pos = EnumTypeDefinitionPosition { + type_name: self.name.clone(), + }; + type_pos.pre_insert(schema)?; + type_pos.insert( + schema, + Node::new(EnumType { + description: None, + name: type_pos.type_name.clone(), + directives: Default::default(), + values: self + .values + .iter() + .map(|val| { + ( + val.name.clone(), + Component::new(EnumValueDefinition { + description: val.description.as_ref().map(|s| s.into()), + value: val.name.clone(), + directives: Default::default(), + }), + ) + }) + .collect(), + }), + ) + } +} + +////////////////////////////////////////////////////////////////////////////// +// DirectiveSpecification + +#[derive(Clone)] +pub(crate) struct DirectiveArgumentSpecification { + pub base_spec: ArgumentSpecification, + pub composition_strategy: Option, +} + +type ArgumentMergerFn = dyn Fn(&str, &[Value]) -> Value; + +pub(crate) struct ArgumentMerger { + pub merge: Box, + pub to_string: Box String>, +} + +type ArgumentMergerFactory = + dyn Fn(&FederationSchema) -> Result; + +pub(crate) struct DirectiveCompositionSpecification { + pub supergraph_specification: fn(federation_version: Version) -> Box, + /// Factory function returning an actual argument merger for given federation schema. + pub argument_merger: Option>, +} + +pub(crate) struct DirectiveSpecification { + pub name: Name, + pub composition: Option, + args: Vec, + repeatable: bool, + locations: Vec, +} + +// TODO: revisit DirectiveSpecification::new() API once we start porting +// composition. +// https://apollographql.atlassian.net/browse/FED-172 +impl DirectiveSpecification { + pub fn new( + name: Name, + args: &[DirectiveArgumentSpecification], + repeatable: bool, + locations: &[DirectiveLocation], + composes: bool, + supergraph_specification: Option< + fn(federation_version: Version) -> Box, + >, + ) -> Self { + let mut composition: Option = None; + if composes { + assert!( supergraph_specification.is_some(), + "Should provide a @link specification to use in supergraph for directive @{name} if it composes"); + let mut argument_merger: Option> = None; + let arg_strategies_iter = args + .iter() + .filter(|arg| arg.composition_strategy.is_some()) + .map(|arg| { + ( + arg.base_spec.name.to_string(), + arg.composition_strategy.unwrap(), + ) + }); + let arg_strategies: IndexMap = + IndexMap::from_iter(arg_strategies_iter); + if !arg_strategies.is_empty() { + assert!(!repeatable, "Invalid directive specification for @{name}: @{name} is repeatable and should not define composition strategy for its arguments"); + assert!(arg_strategies.len() == args.len(), "Invalid directive specification for @{name}: not all arguments define a composition strategy"); + let name_capture = name.clone(); + let args_capture = args.to_vec(); + argument_merger = Some(Box::new(move |schema: &FederationSchema| -> Result { + for arg in args_capture.iter() { + let strategy = arg.composition_strategy.as_ref().unwrap(); + let arg_name = &arg.base_spec.name; + let arg_type = (arg.base_spec.get_type)(schema)?; + assert!(!arg_type.is_list(), "Should have gotten error getting type for @{name_capture}({arg_name}:), but got {arg_type}"); + strategy.is_type_supported(schema, &arg_type).map_err(|support_msg| { + let strategy_name = strategy.name(); + SingleFederationError::DirectiveDefinitionInvalid { + message: format!("Invalid composition strategy {strategy_name} for argument @{name_capture}({arg_name}:) of type {arg_type}; {strategy_name} only supports ${support_msg}") + } + })?; + } + let arg_strategies_capture = arg_strategies.clone(); + let arg_strategies_capture2 = arg_strategies.clone(); + Ok(ArgumentMerger { + merge: Box::new(move |arg_name: &str, values: &[Value]| { + let Some(strategy) = arg_strategies_capture.get(arg_name) else { + panic!("`Should have a strategy for {arg_name}") + }; + strategy.merge_values(values) + }), + to_string: Box::new(move || { + if arg_strategies_capture2.is_empty() { + "".to_string() + } + else { + let arg_strategy_strings: Vec = arg_strategies_capture2 + .iter() + .map(|(arg_name, strategy)| format!("{arg_name}: {}", strategy.name())) + .collect(); + format!("{{ {} }}", arg_strategy_strings.join(", ")) + } + }), + }) + })); + } + composition = Some(DirectiveCompositionSpecification { + supergraph_specification: supergraph_specification.unwrap(), + argument_merger, + }) + } + Self { + name, + composition, + args: args.to_vec(), + repeatable, + locations: locations.to_vec(), + } + } +} + +impl TypeAndDirectiveSpecification for DirectiveSpecification { + fn check_or_add(&self, schema: &mut FederationSchema) -> Result<(), FederationError> { + let mut resolved_args = Vec::new(); + let mut errors = MultipleFederationErrors { errors: vec![] }; + for arg in self.args.iter() { + match (arg.base_spec.get_type)(schema) { + Ok(arg_type) => { + resolved_args.push(ResolvedArgumentSpecification { + name: arg.base_spec.name.clone(), + ty: arg_type, + default_value: arg.base_spec.default_value.clone(), + }); + } + Err(err) => { + errors.errors.push(err); + } + }; + } + errors.into_result()?; + let existing = schema.get_directive_definition(&self.name); + if let Some(existing) = existing { + let existing_directive = existing.get(schema.schema())?; + return ensure_same_directive_structure( + existing_directive, + &self.name, + &resolved_args, + self.repeatable, + &self.locations, + schema, + ); + } + + let directive_pos = DirectiveDefinitionPosition { + directive_name: self.name.clone(), + }; + directive_pos.pre_insert(schema)?; + directive_pos.insert( + schema, + Node::new(DirectiveDefinition { + description: None, + name: self.name.clone(), + arguments: resolved_args + .iter() + .map(|arg| Node::new(arg.into())) + .collect(), + repeatable: self.repeatable, + locations: self.locations.clone(), + }), + ) + } +} + +////////////////////////////////////////////////////////////////////////////// +// Helper functions for TypeSpecification implementations + +// TODO: Consider moving this to the schema module. +#[derive(Clone, PartialEq, Eq, Hash, derive_more::Display)] +pub(crate) enum TypeKind { + Scalar, + Object, + Interface, + Union, + Enum, + InputObject, +} + +impl From<&ExtendedType> for TypeKind { + fn from(value: &ExtendedType) -> Self { + match value { + ExtendedType::Scalar(_) => TypeKind::Scalar, + ExtendedType::Object(_) => TypeKind::Object, + ExtendedType::Interface(_) => TypeKind::Interface, + ExtendedType::Union(_) => TypeKind::Union, + ExtendedType::Enum(_) => TypeKind::Enum, + ExtendedType::InputObject(_) => TypeKind::InputObject, + } + } +} + +impl From<&TypeDefinitionPosition> for TypeKind { + fn from(value: &TypeDefinitionPosition) -> Self { + match value { + TypeDefinitionPosition::Scalar(_) => TypeKind::Scalar, + TypeDefinitionPosition::Object(_) => TypeKind::Object, + TypeDefinitionPosition::Interface(_) => TypeKind::Interface, + TypeDefinitionPosition::Union(_) => TypeKind::Union, + TypeDefinitionPosition::Enum(_) => TypeKind::Enum, + TypeDefinitionPosition::InputObject(_) => TypeKind::InputObject, + } + } +} + +fn ensure_expected_type_kind( + expected: TypeKind, + actual: &TypeDefinitionPosition, +) -> Result<(), FederationError> { + let actual_kind: TypeKind = TypeKind::from(actual); + if expected != actual_kind { + Ok(()) + } else { + let actual_type_name = actual.type_name(); + Err(SingleFederationError::TypeDefinitionInvalid { + message: format!("Invalid definition for type {actual_type_name}: {actual_type_name} should be a {expected} but is defined as a {actual_kind}") + }.into()) + } +} + +/// Note: Non-null/list wrappers are ignored. +fn is_custom_scalar(ty: &Type, schema: &FederationSchema) -> bool { + let type_name = ty.inner_named_type().as_str(); + schema + .schema() + .get_scalar(type_name) + .is_some_and(|scalar| !scalar.is_built_in()) +} + +fn is_valid_input_type_redefinition( + expected_type: &Type, + actual_type: &Type, + schema: &FederationSchema, +) -> bool { + // If the expected type is a custom scalar, then we allow the redefinition to be another type (unless it's a custom scalar, in which + // case it has to be the same scalar). The rational being that since graphQL does no validation of values passed to a custom scalar, + // any code that gets some value as input for a custom scalar has to do validation manually, and so there is little harm in allowing + // a redefinition with another type since any truly invalid value would failed that "manual validation". In practice, this leeway + // make sense because many scalar will tend to accept only one kind of values (say, strings) and exists only to inform that said string + // needs to follow a specific format, and in such case, letting user redefine the type as String adds flexibility while doing little harm. + if expected_type.is_list() { + return actual_type.is_list() + && is_valid_input_type_redefinition( + expected_type.item_type(), + actual_type.item_type(), + schema, + ); + } + if expected_type.is_non_null() { + return actual_type.is_non_null() + && is_valid_input_type_redefinition( + &expected_type.clone().nullable(), + &actual_type.clone().nullable(), + schema, + ); + } + // invariant: expected_type/actual_type is not a list or a non-null type (thus a named type). + is_custom_scalar(expected_type, schema) && !is_custom_scalar(actual_type, schema) +} + +fn default_value_message(value: Option<&Value>) -> String { + match value { + None => "no default value".to_string(), + Some(value) => format!("default value {}", value), + } +} + +fn ensure_same_arguments( + expected: &[Node], + actual: &[ResolvedArgumentSpecification], + schema: &FederationSchema, + what: &str, + generate_error: fn(&str) -> SingleFederationError, +) -> Vec { + let mut errors = vec![]; + + // ensure expected arguments are a subset of actual arguments. + for expected_arg in expected { + let actual_arg = actual.iter().find(|x| x.name == expected_arg.name); + if actual_arg.is_none() { + // Not declaring an optional argument is ok: that means you won't be able to pass a non-default value in your schema, but we allow you that. + // But missing a required argument it not ok. + if expected_arg.ty.is_non_null() && expected_arg.default_value.is_none() { + let expected_arg_name = &expected_arg.name; + errors.push(generate_error(&format!( + r#"Invalid definition for {what}: Missing required argument "{expected_arg_name}""# + ))); + } + continue; + } + + // ensure expected argument and actual argument have the same type. + let actual_arg = actual_arg.unwrap(); + // TODO: Make it easy to get a cloned (inner) type from a Node. + let mut actual_type = actual_arg.ty.clone(); + if actual_type.is_non_null() && !expected_arg.ty.is_non_null() { + // It's ok to redefine an optional argument as mandatory. For instance, if you want to force people on your team to provide a "deprecation reason", you can + // redefine @deprecated as `directive @deprecated(reason: String!)...` to get validation. In other words, you are allowed to always pass an argument that + // is optional if you so wish. + actual_type = actual_type.nullable(); + } + // ensure argument type is compatible with the expected one and + // argument's default value (if any) is compatible with the expected one + if *expected_arg.ty != actual_type + && is_valid_input_type_redefinition(&expected_arg.ty, &actual_type, schema) + { + let arg_name = &expected_arg.name; + let expected_type = &expected_arg.ty; + errors.push(generate_error(&format!( + r#"Invalid definition for {what}: Argument "{arg_name}" should have type {expected_type} but found type {actual_type}"# + ))); + } else if !actual_type.is_non_null() + && expected_arg.default_value.as_deref() != actual_arg.default_value.as_ref() + { + let arg_name = &expected_arg.name; + let expected_value = default_value_message(expected_arg.default_value.as_deref()); + let actual_value = default_value_message(actual_arg.default_value.as_ref()); + errors.push(generate_error(&format!( + r#"Invalid definition for {what}: Argument "{arg_name}" should have {expected_value} but found {actual_value}"# + ))); + } + } + + // ensure actual arguments are a subset of expected arguments. + for actual_arg in actual { + let expected_arg = expected.iter().find(|x| x.name == actual_arg.name); + if expected_arg.is_none() { + let arg_name = &actual_arg.name; + errors.push(generate_error(&format!( + r#"Invalid definition for {what}: unknown/unsupported argument "{arg_name}""# + ))); + // fall through to the next iteration + } + } + + errors +} + +fn ensure_same_fields( + existing_obj_type: &ObjectType, + actual_fields: &[FieldSpecification], + schema: &FederationSchema, +) -> Vec { + let obj_type_name = existing_obj_type.name.clone(); + let mut errors = vec![]; + + // ensure all actual fields are a subset of the existing object type's fields. + for actual_field_def in actual_fields { + let actual_field_name = &actual_field_def.name; + let expected_field = existing_obj_type.fields.get(actual_field_name); + if expected_field.is_none() { + errors.push(SingleFederationError::TypeDefinitionInvalid { + message: format!( + "Invalid definition of type {}: missing field {}", + obj_type_name, actual_field_name + ), + }); + continue; + } + + // ensure field types are as expected + let expected_field = expected_field.unwrap(); + if actual_field_def.ty != expected_field.ty { + let expected_field_type = &expected_field.ty; + let actual_field_type = &actual_field_def.ty; + errors.push(SingleFederationError::TypeDefinitionInvalid { + message: format!("Invalid definition for field {actual_field_name} of type {obj_type_name}: should have type {expected_field_type} but found type {actual_field_type}") + }); + } + + // ensure field arguments are as expected + let mut arg_errors = ensure_same_arguments( + &expected_field.arguments, + &actual_field_def.arguments, + schema, + &format!(r#"field "{}.{}""#, obj_type_name, expected_field.name), + |s| SingleFederationError::TypeDefinitionInvalid { + message: s.to_string(), + }, + ); + errors.append(&mut arg_errors); + } + + errors +} + +fn ensure_same_directive_structure( + existing_directive: &DirectiveDefinition, + name: &Name, + args: &[ResolvedArgumentSpecification], + repeatable: bool, + locations: &[DirectiveLocation], + schema: &FederationSchema, +) -> Result<(), FederationError> { + let directive_name = format!("@{name}"); + let mut arg_errors = ensure_same_arguments( + &existing_directive.arguments, + args, + schema, + &format!(r#"directive {directive_name}"#), + |s| SingleFederationError::DirectiveDefinitionInvalid { + message: s.to_string(), + }, + ); + + // It's ok to say you'll never repeat a repeatable directive. It's not ok to repeat one that isn't. + if !existing_directive.repeatable && repeatable { + arg_errors.push(SingleFederationError::DirectiveDefinitionInvalid { + message: format!( + "Invalid definition for directive {directive_name}: {directive_name} should not be repeatable" + ), + }); + } + + // Similarly, it's ok to say that you will never use a directive in some locations, but not that + // you will use it in places not allowed by what is expected. + // Ensure `locations` is a subset of `existing_directive.locations`. + if !locations + .iter() + .all(|loc| existing_directive.locations.contains(loc)) + { + let actual_locations: Vec = locations.iter().map(|loc| loc.to_string()).collect(); + let existing_locations: Vec = existing_directive + .locations + .iter() + .map(|loc| loc.to_string()) + .collect(); + arg_errors.push(SingleFederationError::DirectiveDefinitionInvalid { + message: format!( + "Invalid definition for directive {directive_name}: {directive_name} should have locations [{}] but found [{}]", + existing_locations.join(", "), actual_locations.join(", ") + ), + }); + } + MultipleFederationErrors::from_iter(arg_errors).into_result() +} + +#[cfg(test)] +mod tests { + use apollo_compiler::ast::DirectiveLocation; + use apollo_compiler::ast::Type; + use apollo_compiler::name; + + use super::ArgumentSpecification; + use super::DirectiveArgumentSpecification; + use crate::error::SingleFederationError; + use crate::link::link_spec_definition::LinkSpecDefinition; + use crate::link::spec::Identity; + use crate::link::spec::Version; + use crate::link::spec_definition::SpecDefinition; + use crate::schema::argument_composition_strategies::ArgumentCompositionStrategy; + use crate::schema::type_and_directive_specification::DirectiveSpecification; + use crate::schema::FederationSchema; + + #[test] + #[should_panic( + expected = "Should provide a @link specification to use in supergraph for directive @foo if it composes" + )] + fn must_have_supergraph_link_if_composed() { + DirectiveSpecification::new( + name!("foo"), + &[], + false, + &[DirectiveLocation::Object], + true, + None, + ); + } + + #[test] + #[should_panic( + expected = "Invalid directive specification for @foo: not all arguments define a composition strategy" + )] + fn must_have_a_merge_strategy_on_all_arguments_if_any() { + fn link_spec(_version: Version) -> Box { + Box::new(LinkSpecDefinition::new( + Version { major: 1, minor: 0 }, + None, + Identity { + domain: String::from("https://specs.apollo.dev/link/v1.0"), + name: name!("link"), + }, + )) + } + + DirectiveSpecification::new( + name!("foo"), + &[ + DirectiveArgumentSpecification { + base_spec: ArgumentSpecification { + name: name!("v1"), + get_type: + move |_schema: &FederationSchema| -> Result { + Ok(Type::Named(name!("Int"))) + }, + default_value: None, + }, + composition_strategy: Some(ArgumentCompositionStrategy::Max), + }, + DirectiveArgumentSpecification { + base_spec: ArgumentSpecification { + name: name!("v2"), + get_type: + move |_schema: &FederationSchema| -> Result { + Ok(Type::Named(name!("Int"))) + }, + default_value: None, + }, + composition_strategy: None, + }, + ], + false, + &[DirectiveLocation::Object], + true, + Some(link_spec) + ); + } + + #[test] + #[should_panic( + expected = "Invalid directive specification for @foo: @foo is repeatable and should not define composition strategy for its arguments" + )] + fn must_be_not_be_repeatable_if_it_has_a_merge_strategy() { + fn link_spec(_version: Version) -> Box { + Box::new(LinkSpecDefinition::new( + Version { major: 1, minor: 0 }, + None, + Identity { + domain: String::from("https://specs.apollo.dev/link/v1.0"), + name: name!("link"), + }, + )) + } + + DirectiveSpecification::new( + name!("foo"), + &[DirectiveArgumentSpecification { + base_spec: ArgumentSpecification { + name: name!("v"), + get_type: + move |_schema: &FederationSchema| -> Result { + Ok(Type::Named(name!("Int"))) + }, + default_value: None, + }, + composition_strategy: Some(ArgumentCompositionStrategy::Max), + }], + true, + &[DirectiveLocation::Object], + true, + Some(link_spec), + ); + } +} diff --git a/apollo-federation/src/subgraph/database.rs b/apollo-federation/src/subgraph/database.rs new file mode 100644 index 0000000000..2745c1e8c9 --- /dev/null +++ b/apollo-federation/src/subgraph/database.rs @@ -0,0 +1,96 @@ +//! Valid federation 2 subgraphs. +//! +//! Note: technically, federation 1 subgraphs are still accepted as input of +//! composition. However, there is some pre-composition steps that "massage" +//! the input schema to transform them in fully valid federation 2 subgraphs, +//! so the subgraphs seen by composition and query planning are always fully +//! valid federation 2 ones, and this is what this database handles. +//! Note2: This does assumes that whichever way an implementation of this +//! trait is created, some validation that the underlying schema is a valid +//! federation subgraph (so valid graphql, link to the federation spec, and +//! pass additional federation validations). If this is not the case, most +//! of the methods here will panic. + +use std::sync::Arc; + +use apollo_compiler::executable::Directive; +use apollo_compiler::executable::SelectionSet; +use apollo_compiler::name; +use apollo_compiler::schema::Name; +use apollo_compiler::Schema; + +use crate::link::database::links_metadata; +use crate::link::spec::Identity; +use crate::link::spec::APOLLO_SPEC_DOMAIN; +use crate::link::Link; + +// TODO: we should define this as part as some more generic "FederationSpec" definition, but need +// to define the ground work for that in `apollo-at-link` first. +pub fn federation_link_identity() -> Identity { + Identity { + domain: APOLLO_SPEC_DOMAIN.to_string(), + name: name!("federation"), + } +} + +#[derive(Eq, PartialEq, Debug, Clone)] +pub struct Key { + pub type_name: Name, + // TODO: this should _not_ be an Option below; but we don't know how to build the SelectionSet, + // so until we have a solution, we use None to have code that compiles. + selections: Option>, +} + +impl Key { + // TODO: same remark as above: not meant to be `Option` + // TODO remove suppression OR use method in final version + #[allow(dead_code)] + pub fn selections(&self) -> Option> { + self.selections.clone() + } + + pub(crate) fn from_directive_application( + type_name: &Name, + directive: &Directive, + ) -> Option { + directive + .arguments + .iter() + .find(|arg| arg.name == "fields") + .and_then(|arg| arg.value.as_str()) + .map(|_value| Key { + type_name: type_name.clone(), + // TODO: obviously not what we want. + selections: None, + }) + } +} + +pub fn federation_link(schema: &Schema) -> Arc { + links_metadata(schema) + // TODO: error handling? + .unwrap_or_default() + .unwrap_or_default() + .for_identity(&federation_link_identity()) + .expect("The presence of the federation link should have been validated on construction") +} + +/// The name of the @key directive in this subgraph. +/// This will either return 'federation__key' if the `@key` directive is not imported, +/// or whatever never it is imported under otherwise. Commonly, this would just be `key`. +pub fn key_directive_name(schema: &Schema) -> Name { + federation_link(schema).directive_name_in_schema(&name!("key")) +} + +pub fn keys(schema: &Schema, type_name: &Name) -> Vec { + let key_name = key_directive_name(schema); + if let Some(type_def) = schema.types.get(type_name) { + type_def + .directives() + .get_all(&key_name) + .filter_map(|directive| Key::from_directive_application(type_name, directive)) + .collect() + } else { + vec![] + } +} diff --git a/apollo-federation/src/subgraph/mod.rs b/apollo-federation/src/subgraph/mod.rs new file mode 100644 index 0000000000..9f3cc35ad0 --- /dev/null +++ b/apollo-federation/src/subgraph/mod.rs @@ -0,0 +1,374 @@ +use std::collections::BTreeMap; +use std::fmt::Formatter; +use std::sync::Arc; + +use apollo_compiler::name; +use apollo_compiler::schema::ComponentName; +use apollo_compiler::schema::ExtendedType; +use apollo_compiler::schema::ObjectType; +use apollo_compiler::validation::Valid; +use apollo_compiler::Node; +use apollo_compiler::Schema; +use indexmap::map::Entry; +use indexmap::IndexMap; +use indexmap::IndexSet; + +use crate::error::FederationError; +use crate::link::spec::Identity; +use crate::link::Link; +use crate::link::LinkError; +use crate::link::DEFAULT_LINK_NAME; +use crate::subgraph::spec::AppliedFederationLink; +use crate::subgraph::spec::FederationSpecDefinitions; +use crate::subgraph::spec::LinkSpecDefinitions; +use crate::subgraph::spec::ANY_SCALAR_NAME; +use crate::subgraph::spec::ENTITIES_QUERY; +use crate::subgraph::spec::ENTITY_UNION_NAME; +use crate::subgraph::spec::FEDERATION_V2_DIRECTIVE_NAMES; +use crate::subgraph::spec::KEY_DIRECTIVE_NAME; +use crate::subgraph::spec::SERVICE_SDL_QUERY; +use crate::subgraph::spec::SERVICE_TYPE; + +mod database; +pub mod spec; + +pub struct Subgraph { + pub name: String, + pub url: String, + pub schema: Schema, +} + +impl Subgraph { + pub fn new(name: &str, url: &str, schema_str: &str) -> Result { + let schema = Schema::parse(schema_str, name)?; + // TODO: federation-specific validation + Ok(Self { + name: name.to_string(), + url: url.to_string(), + schema, + }) + } + + pub fn parse_and_expand( + name: &str, + url: &str, + schema_str: &str, + ) -> Result { + let mut schema = Schema::builder() + .adopt_orphan_extensions() + .parse(schema_str, name) + .build()?; + + let mut imported_federation_definitions: Option = None; + let mut imported_link_definitions: Option = None; + let default_link_name = DEFAULT_LINK_NAME; + let link_directives = schema + .schema_definition + .directives + .get_all(&default_link_name); + + for directive in link_directives { + let link_directive = Link::from_directive_application(directive)?; + if link_directive.url.identity == Identity::federation_identity() { + if imported_federation_definitions.is_some() { + let msg = "invalid graphql schema - multiple @link imports for the federation specification are not supported"; + return Err(LinkError::BootstrapError(msg.to_owned()).into()); + } + + imported_federation_definitions = + Some(FederationSpecDefinitions::from_link(link_directive)?); + } else if link_directive.url.identity == Identity::link_identity() { + // user manually imported @link specification + if imported_link_definitions.is_some() { + let msg = "invalid graphql schema - multiple @link imports for the link specification are not supported"; + return Err(LinkError::BootstrapError(msg.to_owned()).into()); + } + + imported_link_definitions = Some(LinkSpecDefinitions::new(link_directive)); + } + } + + // generate additional schema definitions + Self::populate_missing_type_definitions( + &mut schema, + imported_federation_definitions, + imported_link_definitions, + )?; + let schema = schema.validate()?; + Ok(ValidSubgraph { + name: name.to_owned(), + url: url.to_owned(), + schema, + }) + } + + fn populate_missing_type_definitions( + schema: &mut Schema, + imported_federation_definitions: Option, + imported_link_definitions: Option, + ) -> Result<(), FederationError> { + // populate @link spec definitions + let link_spec_definitions = match imported_link_definitions { + Some(definitions) => definitions, + None => { + // need to apply default @link directive for link spec on schema + let defaults = LinkSpecDefinitions::default(); + schema + .schema_definition + .make_mut() + .directives + .push(defaults.applied_link_directive().into()); + defaults + } + }; + Self::populate_missing_link_definitions(schema, link_spec_definitions)?; + + // populate @link federation spec definitions + let fed_definitions = match imported_federation_definitions { + Some(definitions) => definitions, + None => { + // federation v1 schema or user does not import federation spec + // need to apply default @link directive for federation spec on schema + let defaults = FederationSpecDefinitions::default()?; + schema + .schema_definition + .make_mut() + .directives + .push(defaults.applied_link_directive().into()); + defaults + } + }; + Self::populate_missing_federation_directive_definitions(schema, &fed_definitions)?; + Self::populate_missing_federation_types(schema, &fed_definitions) + } + + fn populate_missing_link_definitions( + schema: &mut Schema, + link_spec_definitions: LinkSpecDefinitions, + ) -> Result<(), FederationError> { + let purpose_enum_name = &link_spec_definitions.purpose_enum_name; + schema + .types + .entry(purpose_enum_name.clone()) + .or_insert_with(|| { + link_spec_definitions + .link_purpose_enum_definition(purpose_enum_name.clone()) + .into() + }); + let import_scalar_name = &link_spec_definitions.import_scalar_name; + schema + .types + .entry(import_scalar_name.clone()) + .or_insert_with(|| { + link_spec_definitions + .import_scalar_definition(import_scalar_name.clone()) + .into() + }); + if let Entry::Vacant(entry) = schema.directive_definitions.entry(DEFAULT_LINK_NAME) { + entry.insert(link_spec_definitions.link_directive_definition()?.into()); + } + Ok(()) + } + + fn populate_missing_federation_directive_definitions( + schema: &mut Schema, + fed_definitions: &FederationSpecDefinitions, + ) -> Result<(), FederationError> { + let fieldset_scalar_name = &fed_definitions.fieldset_scalar_name; + schema + .types + .entry(fieldset_scalar_name.clone()) + .or_insert_with(|| { + fed_definitions + .fieldset_scalar_definition(fieldset_scalar_name.clone()) + .into() + }); + + for directive_name in &FEDERATION_V2_DIRECTIVE_NAMES { + let namespaced_directive_name = + fed_definitions.namespaced_type_name(directive_name, true); + if let Entry::Vacant(entry) = schema + .directive_definitions + .entry(namespaced_directive_name.clone()) + { + let directive_definition = fed_definitions.directive_definition( + directive_name, + &Some(namespaced_directive_name.to_owned()), + )?; + entry.insert(directive_definition.into()); + } + } + Ok(()) + } + + fn populate_missing_federation_types( + schema: &mut Schema, + fed_definitions: &FederationSpecDefinitions, + ) -> Result<(), FederationError> { + schema + .types + .entry(SERVICE_TYPE) + .or_insert_with(|| fed_definitions.service_object_type_definition()); + + let entities = Self::locate_entities(schema, fed_definitions); + let entities_present = !entities.is_empty(); + if entities_present { + schema + .types + .entry(ENTITY_UNION_NAME) + .or_insert_with(|| fed_definitions.entity_union_definition(entities)); + schema + .types + .entry(ANY_SCALAR_NAME) + .or_insert_with(|| fed_definitions.any_scalar_definition()); + } + + let query_type_name = schema + .schema_definition + .make_mut() + .query + .get_or_insert(ComponentName::from(name!("Query"))); + if let ExtendedType::Object(query_type) = schema + .types + .entry(query_type_name.name.clone()) + .or_insert(ExtendedType::Object(Node::new(ObjectType { + description: None, + name: query_type_name.name.clone(), + directives: Default::default(), + fields: IndexMap::new(), + implements_interfaces: IndexSet::new(), + }))) + { + let query_type = query_type.make_mut(); + query_type + .fields + .entry(SERVICE_SDL_QUERY) + .or_insert_with(|| fed_definitions.service_sdl_query_field()); + if entities_present { + // _entities(representations: [_Any!]!): [_Entity]! + query_type + .fields + .entry(ENTITIES_QUERY) + .or_insert_with(|| fed_definitions.entities_query_field()); + } + } + Ok(()) + } + + fn locate_entities( + schema: &mut Schema, + fed_definitions: &FederationSpecDefinitions, + ) -> IndexSet { + let mut entities = Vec::new(); + let immutable_type_map = schema.types.to_owned(); + for (named_type, extended_type) in immutable_type_map.iter() { + let is_entity = extended_type + .directives() + .iter() + .find(|d| { + d.name + == fed_definitions + .namespaced_type_name(&KEY_DIRECTIVE_NAME, true) + .as_str() + }) + .map(|_| true) + .unwrap_or(false); + if is_entity { + entities.push(named_type); + } + } + let entity_set: IndexSet = + entities.iter().map(|e| ComponentName::from(*e)).collect(); + entity_set + } +} + +impl std::fmt::Debug for Subgraph { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, r#"name: {}, urL: {}"#, self.name, self.url) + } +} + +pub struct Subgraphs { + subgraphs: BTreeMap>, +} + +#[allow(clippy::new_without_default)] +impl Subgraphs { + pub fn new() -> Self { + Subgraphs { + subgraphs: BTreeMap::new(), + } + } + + pub fn add(&mut self, subgraph: Subgraph) -> Result<(), String> { + if self.subgraphs.contains_key(&subgraph.name) { + return Err(format!("A subgraph named {} already exists", subgraph.name)); + } + self.subgraphs + .insert(subgraph.name.clone(), Arc::new(subgraph)); + Ok(()) + } + + pub fn get(&self, name: &str) -> Option> { + self.subgraphs.get(name).cloned() + } +} + +pub struct ValidSubgraph { + pub name: String, + pub url: String, + pub schema: Valid, +} + +impl std::fmt::Debug for ValidSubgraph { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, r#"name: {}, url: {}"#, self.name, self.url) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::subgraph::database::keys; + + #[test] + fn can_inspect_a_type_key() { + // TODO: no schema expansion currently, so need to having the `@link` to `link` and the + // @link directive definition for @link-bootstrapping to work. Also, we should + // theoretically have the @key directive definition added too (but validation is not + // wired up yet, so we get away without). Point being, this is just some toy code at + // the moment. + + let schema = r#" + extend schema + @link(url: "https://specs.apollo.dev/link/v1.0", import: ["Import"]) + @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"]) + + type Query { + t: T + } + + type T @key(fields: "id") { + id: ID! + x: Int + } + + enum link__Purpose { + SECURITY + EXECUTION + } + + scalar Import + + directive @link(url: String, as: String, import: [Import], for: link__Purpose) repeatable on SCHEMA + "#; + + let subgraph = Subgraph::new("S1", "http://s1", schema).unwrap(); + let keys = keys(&subgraph.schema, &name!("T")); + assert_eq!(keys.len(), 1); + assert_eq!(keys.first().unwrap().type_name, name!("T")); + + // TODO: no accessible selection yet. + } +} diff --git a/apollo-federation/src/subgraph/spec.rs b/apollo-federation/src/subgraph/spec.rs new file mode 100644 index 0000000000..84651379d5 --- /dev/null +++ b/apollo-federation/src/subgraph/spec.rs @@ -0,0 +1,742 @@ +use std::sync::Arc; + +use apollo_compiler::ast::Argument; +use apollo_compiler::ast::Directive; +use apollo_compiler::ast::DirectiveDefinition; +use apollo_compiler::ast::DirectiveLocation; +use apollo_compiler::ast::EnumValueDefinition; +use apollo_compiler::ast::FieldDefinition; +use apollo_compiler::ast::InputValueDefinition; +use apollo_compiler::ast::InvalidNameError; +use apollo_compiler::ast::Name; +use apollo_compiler::ast::Type; +use apollo_compiler::ast::Value; +use apollo_compiler::name; +use apollo_compiler::schema::Component; +use apollo_compiler::schema::ComponentName; +use apollo_compiler::schema::EnumType; +use apollo_compiler::schema::ExtendedType; +use apollo_compiler::schema::ObjectType; +use apollo_compiler::schema::ScalarType; +use apollo_compiler::schema::UnionType; +use apollo_compiler::ty; +use apollo_compiler::Node; +use apollo_compiler::NodeStr; +use indexmap::IndexMap; +use indexmap::IndexSet; +use lazy_static::lazy_static; +use thiserror::Error; + +use crate::link::spec::Identity; +use crate::link::spec::Url; +use crate::link::spec::Version; +use crate::link::Import; +use crate::link::Link; +use crate::link::DEFAULT_IMPORT_SCALAR_NAME; +use crate::link::DEFAULT_LINK_NAME; +use crate::link::DEFAULT_PURPOSE_ENUM_NAME; +use crate::subgraph::spec::FederationSpecError::UnsupportedFederationDirective; +use crate::subgraph::spec::FederationSpecError::UnsupportedVersionError; + +pub const COMPOSE_DIRECTIVE_NAME: Name = name!("composeDirective"); +pub const KEY_DIRECTIVE_NAME: Name = name!("key"); +pub const EXTENDS_DIRECTIVE_NAME: Name = name!("extends"); +pub const EXTERNAL_DIRECTIVE_NAME: Name = name!("external"); +pub const INACCESSIBLE_DIRECTIVE_NAME: Name = name!("inaccessible"); +pub const INTF_OBJECT_DIRECTIVE_NAME: Name = name!("interfaceObject"); +pub const OVERRIDE_DIRECTIVE_NAME: Name = name!("override"); +pub const PROVIDES_DIRECTIVE_NAME: Name = name!("provides"); +pub const REQUIRES_DIRECTIVE_NAME: Name = name!("requires"); +pub const SHAREABLE_DIRECTIVE_NAME: Name = name!("shareable"); +pub const TAG_DIRECTIVE_NAME: Name = name!("tag"); +pub const FIELDSET_SCALAR_NAME: Name = name!("FieldSet"); + +// federated types +pub const ANY_SCALAR_NAME: Name = name!("_Any"); +pub const ENTITY_UNION_NAME: Name = name!("_Entity"); +pub const SERVICE_TYPE: Name = name!("_Service"); + +pub const ENTITIES_QUERY: Name = name!("_entities"); +pub const SERVICE_SDL_QUERY: Name = name!("_service"); + +pub const FEDERATION_V1_DIRECTIVE_NAMES: [Name; 5] = [ + KEY_DIRECTIVE_NAME, + EXTENDS_DIRECTIVE_NAME, + EXTERNAL_DIRECTIVE_NAME, + PROVIDES_DIRECTIVE_NAME, + REQUIRES_DIRECTIVE_NAME, +]; + +pub const FEDERATION_V2_DIRECTIVE_NAMES: [Name; 11] = [ + COMPOSE_DIRECTIVE_NAME, + KEY_DIRECTIVE_NAME, + EXTENDS_DIRECTIVE_NAME, + EXTERNAL_DIRECTIVE_NAME, + INACCESSIBLE_DIRECTIVE_NAME, + INTF_OBJECT_DIRECTIVE_NAME, + OVERRIDE_DIRECTIVE_NAME, + PROVIDES_DIRECTIVE_NAME, + REQUIRES_DIRECTIVE_NAME, + SHAREABLE_DIRECTIVE_NAME, + TAG_DIRECTIVE_NAME, +]; + +// This type and the subsequent IndexMap exist purely so we can use match with Names; see comment +// in FederationSpecDefinitions.directive_definition() for more information. +enum FederationDirectiveName { + Compose, + Key, + Extends, + External, + Inaccessible, + IntfObject, + Override, + Provides, + Requires, + Shareable, + Tag, +} + +lazy_static! { + static ref FEDERATION_DIRECTIVE_NAMES_TO_ENUM: IndexMap = { + IndexMap::from([ + (COMPOSE_DIRECTIVE_NAME, FederationDirectiveName::Compose), + (KEY_DIRECTIVE_NAME, FederationDirectiveName::Key), + (EXTENDS_DIRECTIVE_NAME, FederationDirectiveName::Extends), + (EXTERNAL_DIRECTIVE_NAME, FederationDirectiveName::External), + ( + INACCESSIBLE_DIRECTIVE_NAME, + FederationDirectiveName::Inaccessible, + ), + ( + INTF_OBJECT_DIRECTIVE_NAME, + FederationDirectiveName::IntfObject, + ), + (OVERRIDE_DIRECTIVE_NAME, FederationDirectiveName::Override), + (PROVIDES_DIRECTIVE_NAME, FederationDirectiveName::Provides), + (REQUIRES_DIRECTIVE_NAME, FederationDirectiveName::Requires), + (SHAREABLE_DIRECTIVE_NAME, FederationDirectiveName::Shareable), + (TAG_DIRECTIVE_NAME, FederationDirectiveName::Tag), + ]) + }; +} + +const MIN_FEDERATION_VERSION: Version = Version { major: 2, minor: 0 }; +const MAX_FEDERATION_VERSION: Version = Version { major: 2, minor: 5 }; + +#[derive(Error, Debug, PartialEq)] +pub enum FederationSpecError { + #[error( + "Specified specification version {specified} is outside of supported range {min}-{max}" + )] + UnsupportedVersionError { + specified: String, + min: String, + max: String, + }, + #[error("Unsupported federation directive import {0}")] + UnsupportedFederationDirective(String), + #[error("Invalid GraphQL name {0}")] + InvalidGraphQLName(String), +} + +impl From for FederationSpecError { + fn from(err: InvalidNameError) -> Self { + FederationSpecError::InvalidGraphQLName(format!("Invalid GraphQL name \"{}\"", err.0)) + } +} + +#[derive(Debug)] +pub struct FederationSpecDefinitions { + link: Link, + pub fieldset_scalar_name: Name, +} + +#[derive(Debug)] +pub struct LinkSpecDefinitions { + link: Link, + pub import_scalar_name: Name, + pub purpose_enum_name: Name, +} + +pub trait AppliedFederationLink { + fn applied_link_directive(&self) -> Directive; +} + +macro_rules! applied_specification { + ($($t:ty),+) => { + $(impl AppliedFederationLink for $t { + /// ```graphql + /// @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"]) + /// ``` + fn applied_link_directive(&self) -> Directive { + let imports = self + .link + .imports + .iter() + .map(|i| { + if i.alias.is_some() { + Value::Object(vec![ + (name!("name"), i.element.as_str().into()), + (name!("as"), i.imported_display_name().to_string().into()), + ]) + } else { + i.imported_display_name().to_string().into() + }.into() + }) + .collect::>>(); + let mut applied_link_directive = Directive { + name: DEFAULT_LINK_NAME, + arguments: vec![ + Argument { + name: name!("url"), + value: self.link.url.to_string().into(), + }.into(), + Argument { + name: name!("import"), + value: Value::List(imports).into(), + }.into(), + ] + }; + if let Some(spec_alias) = &self.link.spec_alias { + applied_link_directive.arguments.push(Argument { + name: name!("as"), + // TODO `spec_alias.into()` when https://github.com/apollographql/apollo-rs/pull/773 is released + value: Value::String(>::as_ref(&spec_alias).clone()).into(), + }.into()) + } + if let Some(purpose) = &self.link.purpose { + applied_link_directive.arguments.push(Argument { + name: name!("for"), + value: Value::Enum(purpose.into()).into(), + }.into()) + } + applied_link_directive + } + })+ + } +} + +applied_specification!(FederationSpecDefinitions, LinkSpecDefinitions); + +impl FederationSpecDefinitions { + pub fn from_link(link: Link) -> Result { + if !link + .url + .version + .satisfies_range(&MIN_FEDERATION_VERSION, &MAX_FEDERATION_VERSION) + { + Err(UnsupportedVersionError { + specified: link.url.version.to_string(), + min: MIN_FEDERATION_VERSION.to_string(), + max: MAX_FEDERATION_VERSION.to_string(), + }) + } else { + let fieldset_scalar_name = link.type_name_in_schema(&FIELDSET_SCALAR_NAME); + Ok(Self { + link, + fieldset_scalar_name, + }) + } + } + + // The Default trait doesn't allow for returning Results, so we ignore the clippy warning here. + #[allow(clippy::should_implement_trait)] + pub fn default() -> Result { + Self::from_link(Link { + url: Url { + identity: Identity::federation_identity(), + version: MAX_FEDERATION_VERSION, + }, + imports: FEDERATION_V1_DIRECTIVE_NAMES + .iter() + .map(|i| { + Arc::new(Import { + element: i.clone(), + alias: None, + is_directive: true, + }) + }) + .collect::>>(), + purpose: None, + spec_alias: None, + }) + } + + pub fn namespaced_type_name(&self, name: &Name, is_directive: bool) -> Name { + if is_directive { + self.link.directive_name_in_schema(name) + } else { + self.link.type_name_in_schema(name) + } + } + + pub fn directive_definition( + &self, + name: &Name, + alias: &Option, + ) -> Result { + // TODO: NodeStr is not annotated with #[derive(PartialEq, Eq)], so Clippy warns it should + // not be used in pattern matching (as some future Rust version will likely turn this into + // a hard error). We resort instead to indexing into a static IndexMap to get an enum, which + // can be used in a match. + let Some(enum_name) = FEDERATION_DIRECTIVE_NAMES_TO_ENUM.get(name) else { + return Err(UnsupportedFederationDirective(name.to_string())); + }; + Ok(match enum_name { + FederationDirectiveName::Compose => self.compose_directive_definition(alias), + FederationDirectiveName::Key => self.key_directive_definition(alias)?, + FederationDirectiveName::Extends => self.extends_directive_definition(alias), + FederationDirectiveName::External => self.external_directive_definition(alias), + FederationDirectiveName::Inaccessible => self.inaccessible_directive_definition(alias), + FederationDirectiveName::IntfObject => { + self.interface_object_directive_definition(alias) + } + FederationDirectiveName::Override => self.override_directive_definition(alias), + FederationDirectiveName::Provides => self.provides_directive_definition(alias)?, + FederationDirectiveName::Requires => self.requires_directive_definition(alias)?, + FederationDirectiveName::Shareable => self.shareable_directive_definition(alias), + FederationDirectiveName::Tag => self.tag_directive_definition(alias), + }) + } + + /// scalar FieldSet + pub fn fieldset_scalar_definition(&self, name: Name) -> ScalarType { + ScalarType { + description: None, + name, + directives: Default::default(), + } + } + + fn fields_argument_definition(&self) -> Result { + Ok(InputValueDefinition { + description: None, + name: name!("fields"), + ty: Type::Named(self.namespaced_type_name(&FIELDSET_SCALAR_NAME, false)) + .non_null() + .into(), + default_value: None, + directives: Default::default(), + }) + } + + /// directive @composeDirective(name: String!) repeatable on SCHEMA + fn compose_directive_definition(&self, alias: &Option) -> DirectiveDefinition { + DirectiveDefinition { + description: None, + name: alias.clone().unwrap_or(COMPOSE_DIRECTIVE_NAME), + arguments: vec![InputValueDefinition { + description: None, + name: name!("name"), + ty: ty!(String!).into(), + default_value: None, + directives: Default::default(), + } + .into()], + repeatable: true, + locations: vec![DirectiveLocation::Schema], + } + } + + /// directive @key(fields: FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE + fn key_directive_definition( + &self, + alias: &Option, + ) -> Result { + Ok(DirectiveDefinition { + description: None, + name: alias.clone().unwrap_or(KEY_DIRECTIVE_NAME), + arguments: vec![ + self.fields_argument_definition()?.into(), + InputValueDefinition { + description: None, + name: name!("resolvable"), + ty: ty!(Boolean).into(), + default_value: Some(true.into()), + directives: Default::default(), + } + .into(), + ], + repeatable: true, + locations: vec![DirectiveLocation::Object, DirectiveLocation::Interface], + }) + } + + /// directive @extends on OBJECT | INTERFACE + fn extends_directive_definition(&self, alias: &Option) -> DirectiveDefinition { + DirectiveDefinition { + description: None, + name: alias.clone().unwrap_or(EXTENDS_DIRECTIVE_NAME), + arguments: Vec::new(), + repeatable: false, + locations: vec![DirectiveLocation::Object, DirectiveLocation::Interface], + } + } + + /// directive @external on OBJECT | FIELD_DEFINITION + fn external_directive_definition(&self, alias: &Option) -> DirectiveDefinition { + DirectiveDefinition { + description: None, + name: alias.clone().unwrap_or(EXTERNAL_DIRECTIVE_NAME), + arguments: Vec::new(), + repeatable: false, + locations: vec![ + DirectiveLocation::Object, + DirectiveLocation::FieldDefinition, + ], + } + } + + /// directive @inaccessible on + /// | ARGUMENT_DEFINITION + /// | ENUM + /// | ENUM_VALUE + /// | FIELD_DEFINITION + /// | INPUT_FIELD_DEFINITION + /// | INPUT_OBJECT + /// | INTERFACE + /// | OBJECT + /// | SCALAR + /// | UNION + fn inaccessible_directive_definition(&self, alias: &Option) -> DirectiveDefinition { + DirectiveDefinition { + description: None, + name: alias.clone().unwrap_or(INACCESSIBLE_DIRECTIVE_NAME), + arguments: Vec::new(), + repeatable: false, + locations: vec![ + DirectiveLocation::ArgumentDefinition, + DirectiveLocation::Enum, + DirectiveLocation::EnumValue, + DirectiveLocation::FieldDefinition, + DirectiveLocation::InputFieldDefinition, + DirectiveLocation::InputObject, + DirectiveLocation::Interface, + DirectiveLocation::Object, + DirectiveLocation::Scalar, + DirectiveLocation::Union, + ], + } + } + + /// directive @interfaceObject on OBJECT + fn interface_object_directive_definition(&self, alias: &Option) -> DirectiveDefinition { + DirectiveDefinition { + description: None, + name: alias.clone().unwrap_or(INTF_OBJECT_DIRECTIVE_NAME), + arguments: Vec::new(), + repeatable: false, + locations: vec![DirectiveLocation::Object], + } + } + + /// directive @override(from: String!) on FIELD_DEFINITION + fn override_directive_definition(&self, alias: &Option) -> DirectiveDefinition { + DirectiveDefinition { + description: None, + name: alias.clone().unwrap_or(OVERRIDE_DIRECTIVE_NAME), + arguments: vec![InputValueDefinition { + description: None, + name: name!("from"), + ty: ty!(String!).into(), + default_value: None, + directives: Default::default(), + } + .into()], + repeatable: false, + locations: vec![DirectiveLocation::FieldDefinition], + } + } + + /// directive @provides(fields: FieldSet!) on FIELD_DEFINITION + fn provides_directive_definition( + &self, + alias: &Option, + ) -> Result { + Ok(DirectiveDefinition { + description: None, + name: alias.clone().unwrap_or(PROVIDES_DIRECTIVE_NAME), + arguments: vec![self.fields_argument_definition()?.into()], + repeatable: false, + locations: vec![DirectiveLocation::FieldDefinition], + }) + } + + /// directive @requires(fields: FieldSet!) on FIELD_DEFINITION + fn requires_directive_definition( + &self, + alias: &Option, + ) -> Result { + Ok(DirectiveDefinition { + description: None, + name: alias.clone().unwrap_or(REQUIRES_DIRECTIVE_NAME), + arguments: vec![self.fields_argument_definition()?.into()], + repeatable: false, + locations: vec![DirectiveLocation::FieldDefinition], + }) + } + + /// directive @shareable repeatable on FIELD_DEFINITION | OBJECT + fn shareable_directive_definition(&self, alias: &Option) -> DirectiveDefinition { + DirectiveDefinition { + description: None, + name: alias.clone().unwrap_or(SHAREABLE_DIRECTIVE_NAME), + arguments: Vec::new(), + repeatable: true, + locations: vec![ + DirectiveLocation::FieldDefinition, + DirectiveLocation::Object, + ], + } + } + + /// directive @tag(name: String!) repeatable on + /// | ARGUMENT_DEFINITION + /// | ENUM + /// | ENUM_VALUE + /// | FIELD_DEFINITION + /// | INPUT_FIELD_DEFINITION + /// | INPUT_OBJECT + /// | INTERFACE + /// | OBJECT + /// | SCALAR + /// | UNION + fn tag_directive_definition(&self, alias: &Option) -> DirectiveDefinition { + DirectiveDefinition { + description: None, + name: alias.clone().unwrap_or(TAG_DIRECTIVE_NAME), + arguments: vec![InputValueDefinition { + description: None, + name: name!("name"), + ty: ty!(String!).into(), + default_value: None, + directives: Default::default(), + } + .into()], + repeatable: true, + locations: vec![ + DirectiveLocation::ArgumentDefinition, + DirectiveLocation::Enum, + DirectiveLocation::EnumValue, + DirectiveLocation::FieldDefinition, + DirectiveLocation::InputFieldDefinition, + DirectiveLocation::InputObject, + DirectiveLocation::Interface, + DirectiveLocation::Object, + DirectiveLocation::Scalar, + DirectiveLocation::Union, + ], + } + } + + pub(crate) fn any_scalar_definition(&self) -> ExtendedType { + let any_scalar = ScalarType { + description: None, + name: ANY_SCALAR_NAME, + directives: Default::default(), + }; + ExtendedType::Scalar(Node::new(any_scalar)) + } + + pub(crate) fn entity_union_definition( + &self, + entities: IndexSet, + ) -> ExtendedType { + let service_type = UnionType { + description: None, + name: ENTITY_UNION_NAME, + directives: Default::default(), + members: entities, + }; + ExtendedType::Union(Node::new(service_type)) + } + pub(crate) fn service_object_type_definition(&self) -> ExtendedType { + let mut service_type = ObjectType { + description: None, + name: SERVICE_TYPE, + directives: Default::default(), + fields: IndexMap::new(), + implements_interfaces: IndexSet::new(), + }; + service_type.fields.insert( + name!("_sdl"), + Component::new(FieldDefinition { + name: name!("_sdl"), + description: None, + directives: Default::default(), + arguments: Vec::new(), + ty: ty!(String), + }), + ); + ExtendedType::Object(Node::new(service_type)) + } + + pub(crate) fn entities_query_field(&self) -> Component { + Component::new(FieldDefinition { + name: ENTITIES_QUERY, + description: None, + directives: Default::default(), + arguments: vec![Node::new(InputValueDefinition { + name: name!("representations"), + description: None, + directives: Default::default(), + ty: Node::new(Type::NonNullList(Box::new(Type::NonNullNamed( + ANY_SCALAR_NAME, + )))), + default_value: None, + })], + ty: Type::NonNullList(Box::new(Type::Named(ENTITY_UNION_NAME))), + }) + } + + pub(crate) fn service_sdl_query_field(&self) -> Component { + Component::new(FieldDefinition { + name: SERVICE_SDL_QUERY, + description: None, + directives: Default::default(), + arguments: Vec::new(), + ty: Type::NonNullNamed(SERVICE_TYPE), + }) + } +} + +impl LinkSpecDefinitions { + pub fn new(link: Link) -> Self { + let import_scalar_name = link.type_name_in_schema(&DEFAULT_IMPORT_SCALAR_NAME); + let purpose_enum_name = link.type_name_in_schema(&DEFAULT_PURPOSE_ENUM_NAME); + Self { + link, + import_scalar_name, + purpose_enum_name, + } + } + + /// scalar Import + pub fn import_scalar_definition(&self, name: Name) -> ScalarType { + ScalarType { + description: None, + name, + directives: Default::default(), + } + } + + /// enum link__Purpose { + /// SECURITY + /// EXECUTION + /// } + pub fn link_purpose_enum_definition(&self, name: Name) -> EnumType { + EnumType { + description: None, + name, + directives: Default::default(), + values: [ + ( + name!("SECURITY"), + EnumValueDefinition { + description: None, + value: name!("SECURITY"), + directives: Default::default(), + } + .into(), + ), + ( + name!("EXECUTION"), + EnumValueDefinition { + description: None, + value: name!("EXECUTION"), + directives: Default::default(), + } + .into(), + ), + ] + .into(), + } + } + + /// directive @link(url: String!, as: String, import: [Import], for: link__Purpose) repeatable on SCHEMA + pub fn link_directive_definition(&self) -> Result { + Ok(DirectiveDefinition { + description: None, + name: DEFAULT_LINK_NAME, + arguments: vec![ + InputValueDefinition { + description: None, + name: name!("url"), + ty: ty!(String!).into(), + default_value: None, + directives: Default::default(), + } + .into(), + InputValueDefinition { + description: None, + name: name!("as"), + ty: ty!(String).into(), + default_value: None, + directives: Default::default(), + } + .into(), + InputValueDefinition { + description: None, + name: name!("import"), + ty: Type::Named(self.import_scalar_name.clone()).list().into(), + default_value: None, + directives: Default::default(), + } + .into(), + InputValueDefinition { + description: None, + name: name!("for"), + ty: Type::Named(self.purpose_enum_name.clone()).into(), + default_value: None, + directives: Default::default(), + } + .into(), + ], + repeatable: true, + locations: vec![DirectiveLocation::Schema], + }) + } +} + +impl Default for LinkSpecDefinitions { + fn default() -> Self { + let link = Link { + url: Url { + identity: Identity::link_identity(), + version: Version { major: 1, minor: 0 }, + }, + imports: vec![Arc::new(Import { + element: name!("Import"), + is_directive: false, + alias: None, + })], + purpose: None, + spec_alias: None, + }; + Self::new(link) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::subgraph::database::federation_link_identity; + + #[test] + fn handle_unsupported_federation_version() { + FederationSpecDefinitions::from_link(Link { + url: Url { + identity: federation_link_identity(), + version: Version { + major: 99, + minor: 99, + }, + }, + spec_alias: None, + imports: vec![], + purpose: None, + }) + .expect_err("federation version 99 is not yet supported"); + } +} diff --git a/apollo-federation/tests/api_schema.rs b/apollo-federation/tests/api_schema.rs new file mode 100644 index 0000000000..7dffa92fe5 --- /dev/null +++ b/apollo-federation/tests/api_schema.rs @@ -0,0 +1,2472 @@ +use apollo_compiler::coord; +use apollo_compiler::schema::ExtendedType; +use apollo_compiler::validation::Valid; +use apollo_compiler::Schema; +use apollo_federation::error::FederationError; +use apollo_federation::ApiSchemaOptions; +use apollo_federation::Supergraph; + +// TODO(@goto-bus-stop): inaccessible is in theory a standalone spec, +// but is only tested here as part of API schema, unlike in the JS implementation. +// This means that all test inputs must be valid supergraphs. +// Ideally we would pull out the inaccessible tests to only apply +// `InaccessibleSpecDefinition::remove_inaccessible_elements` to a `FederationSchema`, +// and remove the supergraph-specific `@link`s (`join`) below. +const INACCESSIBLE_V02_HEADER: &str = r#" + directive @link(url: String!, as: String, import: [link__Import], for: link__Purpose) repeatable on SCHEMA + + scalar link__Import + + enum link__Purpose { + EXECUTION + SECURITY + } + + directive @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION + + schema + @link(url: "https://specs.apollo.dev/link/v1.0") + @link(url: "https://specs.apollo.dev/join/v0.2", for: EXECUTION) + @link(url: "https://specs.apollo.dev/inaccessible/v0.2") + { + query: Query + } +"#; + +fn inaccessible_to_api_schema(input: &str) -> Result, FederationError> { + let sdl = format!("{INACCESSIBLE_V02_HEADER}{input}"); + let graph = Supergraph::new(&sdl)?; + Ok(graph.to_api_schema(Default::default())?.schema().clone()) +} + +#[test] +fn inaccessible_types_with_accessible_references() { + let errors = inaccessible_to_api_schema( + r#" + # Query types can't be inaccessible + type Query @inaccessible { + someField: String + } + + # Inaccessible object type + type Object @inaccessible { + someField: String + } + + # Inaccessible object type can't be referenced by object field in the API + # schema + type Referencer1 implements Referencer2 { + someField: Object! + } + + # Inaccessible object type can't be referenced by interface field in the + # API schema + interface Referencer2 { + someField: Object + } + + # Inaccessible object type can't be referenced by union member with a + # non-inaccessible parent and no non-inaccessible siblings + union Referencer3 = Object + "#, + ) + .expect_err("should return validation errors"); + + insta::assert_snapshot!(errors, @r###" + The following errors occurred: + + - Type `Query` is @inaccessible but is the query root type, which must be in the API schema. + + - Type `Object` is @inaccessible but is referenced by `Referencer1.someField`, which is in the API schema. + + - Type `Object` is @inaccessible but is referenced by `Referencer2.someField`, which is in the API schema. + + - Type `Referencer3` is in the API schema but all of its members are @inaccessible. + "###); +} + +#[test] +fn removes_inaccessible_object_types() { + let api_schema = inaccessible_to_api_schema( + r#" + extend schema { + mutation: Mutation + subscription: Subscription + } + + # Non-inaccessible object type + type Query { + someField: String + } + + # Inaccessible mutation types should be removed + type Mutation @inaccessible { + someObject: Object + } + + # Inaccessible subscription types should be removed + type Subscription @inaccessible { + someField: String + } + + # Inaccessible object type + type Object @inaccessible { + someField: String + } + + # Inaccessible object type referenced by inaccessible object field + type Referencer1 implements Referencer3 { + someField: String + privatefield: Object! @inaccessible + } + + # Inaccessible object type referenced by non-inaccessible object field + # with inaccessible parent + type Referencer2 implements Referencer4 @inaccessible { + privateField: [Object!]! + } + + # Inaccessible object type referenced by inaccessible interface field + interface Referencer3 { + someField: String + privatefield: Object @inaccessible + } + + # Inaccessible object type referenced by non-inaccessible interface field + # with inaccessible parent + interface Referencer4 @inaccessible { + privateField: [Object] + } + + # Inaccessible object type referenced by union member with + # non-inaccessible siblings and parent + union Referencer5 = Query | Object + + # Inaccessible object type referenced by union member with no siblings + # but with inaccessible parent + union Referencer6 @inaccessible = Object + "#, + ) + .expect("should succeed"); + + assert!(api_schema.types.contains_key("Query")); + assert!(!api_schema.types.contains_key("Mutation")); + assert!(!api_schema.types.contains_key("Subscription")); + assert!(!api_schema.types.contains_key("Object")); + assert!(coord!(Referencer1.someField).lookup(&api_schema).is_ok()); + assert!(coord!(Referencer1.privatefield) + .lookup(&api_schema) + .is_err()); + assert!(!api_schema.types.contains_key("Referencer2")); + assert!(coord!(Referencer3.someField).lookup(&api_schema).is_ok()); + assert!(coord!(Referencer3.privatefield) + .lookup(&api_schema) + .is_err()); + assert!(!api_schema.types.contains_key("Referencer4")); + + let ExtendedType::Union(union_) = api_schema.types.get("Referencer5").unwrap() else { + panic!("expected union"); + }; + assert!(union_.members.contains("Query")); + assert!(!union_.members.contains("Object")); + assert!(!api_schema.types.contains_key("Referencer6")); +} + +#[test] +fn inaccessible_interface_with_accessible_references() { + let errors = inaccessible_to_api_schema( + r#" + type Query { + someField: String + } + + # Inaccessible interface type + interface Interface @inaccessible { + someField: String + } + + # Inaccessible interface type can't be referenced by object field in the + # API schema + type Referencer1 implements Referencer2 { + someField: [Interface!]! + } + + # Inaccessible interface type can't be referenced by interface field in + # the API schema + interface Referencer2 { + someField: [Interface] + } + "#, + ) + .expect_err("should return validation errors"); + + insta::assert_snapshot!(errors, @r###" + The following errors occurred: + + - Type `Interface` is @inaccessible but is referenced by `Referencer1.someField`, which is in the API schema. + + - Type `Interface` is @inaccessible but is referenced by `Referencer2.someField`, which is in the API schema. + "###); +} + +#[test] +fn removes_inaccessible_interface_types() { + let api_schema = inaccessible_to_api_schema( + r#" + type Query { + someField: String + } + + # Non-inaccessible interface type + interface VisibleInterface { + someField: String + } + + # Inaccessible interface type + interface Interface @inaccessible { + someField: String + } + + # Inaccessible interface type referenced by inaccessible object field + type Referencer1 implements Referencer3 { + someField: String + privatefield: Interface! @inaccessible + } + + # Inaccessible interface type referenced by non-inaccessible object field + # with inaccessible parent + type Referencer2 implements Referencer4 @inaccessible { + privateField: [Interface!]! + } + + # Inaccessible interface type referenced by inaccessible interface field + interface Referencer3 { + someField: String + privatefield: Interface @inaccessible + } + + # Inaccessible interface type referenced by non-inaccessible interface + # field with inaccessible parent + interface Referencer4 @inaccessible { + privateField: [Interface] + } + + # Inaccessible interface type referenced by object type implements + type Referencer5 implements VisibleInterface & Interface { + someField: String + } + + # Inaccessible interface type referenced by interface type implements + interface Referencer6 implements VisibleInterface & Interface { + someField: String + } + "#, + ) + .expect("should succeed"); + + assert!(api_schema.types.contains_key("VisibleInterface")); + assert!(!api_schema.types.contains_key("Interface")); + assert!(!api_schema.types.contains_key("Object")); + assert!(api_schema.type_field("Referencer1", "someField").is_ok()); + assert!(api_schema + .type_field("Referencer1", "privatefield") + .is_err()); + assert!(!api_schema.types.contains_key("Referencer2")); + assert!(api_schema.type_field("Referencer3", "someField").is_ok()); + assert!(api_schema + .type_field("Referencer3", "privatefield") + .is_err()); + assert!(!api_schema.types.contains_key("Referencer4")); + + let ExtendedType::Object(object) = api_schema.types.get("Referencer5").unwrap() else { + panic!("expected object"); + }; + assert!(object.implements_interfaces.contains("VisibleInterface")); + assert!(!object.implements_interfaces.contains("Interface")); + + let ExtendedType::Interface(interface) = api_schema.types.get("Referencer6").unwrap() else { + panic!("expected interface"); + }; + assert!(interface.implements_interfaces.contains("VisibleInterface")); + assert!(!interface.implements_interfaces.contains("Interface")); +} + +#[test] +fn inaccessible_union_with_accessible_references() { + let errors = inaccessible_to_api_schema( + r#" + type Query { + someField: String + } + + # Inaccessible union type + union Union @inaccessible = Query + + # Inaccessible union type can't be referenced by object field in the API + # schema + type Referencer1 implements Referencer2 { + someField: Union! + } + + # Inaccessible union type can't be referenced by interface field in the + # API schema + interface Referencer2 { + someField: Union + } + "#, + ) + .expect_err("should return validation errors"); + + insta::assert_snapshot!(errors, @r###" + The following errors occurred: + + - Type `Union` is @inaccessible but is referenced by `Referencer1.someField`, which is in the API schema. + + - Type `Union` is @inaccessible but is referenced by `Referencer2.someField`, which is in the API schema. + "###); +} + +#[test] +fn removes_inaccessible_union_types() { + let api_schema = inaccessible_to_api_schema( + r#" + type Query { + someField: String + } + + # Non-inaccessible union type + union VisibleUnion = Query + + # Inaccessible union type + union Union @inaccessible = Query + + # Inaccessible union type referenced by inaccessible object field + type Referencer1 implements Referencer3 { + someField: String + privatefield: Union! @inaccessible + } + + # Inaccessible union type referenced by non-inaccessible object field with + # inaccessible parent + type Referencer2 implements Referencer4 @inaccessible { + privateField: [Union!]! + } + + # Inaccessible union type referenced by inaccessible interface field + interface Referencer3 { + someField: String + privatefield: Union @inaccessible + } + + # Inaccessible union type referenced by non-inaccessible interface field + # with inaccessible parent + interface Referencer4 @inaccessible { + privateField: [Union] + } + "#, + ) + .expect("should succeed"); + + assert!(api_schema.types.contains_key("VisibleUnion")); + assert!(!api_schema.types.contains_key("Union")); + assert!(!api_schema.types.contains_key("Object")); + assert!(api_schema.type_field("Referencer1", "someField").is_ok()); + assert!(api_schema + .type_field("Referencer1", "privatefield") + .is_err()); + assert!(!api_schema.types.contains_key("Referencer2")); + assert!(api_schema.type_field("Referencer3", "someField").is_ok()); + assert!(api_schema + .type_field("Referencer3", "privatefield") + .is_err()); + assert!(!api_schema.types.contains_key("Referencer4")); +} + +#[test] +fn inaccessible_input_object_with_accessible_references() { + let errors = inaccessible_to_api_schema( + r#" + type Query { + someField: String + } + + # Inaccessible input object type + input InputObject @inaccessible { + someField: String + } + + # Inaccessible input object type can't be referenced by object field + # argument in the API schema + type Referencer1 implements Referencer2 { + someField(someArg: InputObject): String + } + + # Inaccessible input object type can't be referenced by interface field + # argument in the API schema + interface Referencer2 { + someField(someArg: InputObject): String + } + + # Inaccessible input object type can't be referenced by input object field + # in the API schema + input Referencer3 { + someField: InputObject + } + + # Inaccessible input object type can't be referenced by directive argument + # in the API schema + directive @referencer4(someArg: InputObject) on QUERY + "#, + ) + .expect_err("should return validation errors"); + + insta::assert_snapshot!(errors, @r###" + The following errors occurred: + + - Type `InputObject` is @inaccessible but is referenced by `Referencer3.someField`, which is in the API schema. + + - Type `InputObject` is @inaccessible but is referenced by `Referencer1.someField(someArg:)`, which is in the API schema. + + - Type `InputObject` is @inaccessible but is referenced by `Referencer2.someField(someArg:)`, which is in the API schema. + + - Type `InputObject` is @inaccessible but is referenced by `@referencer4(someArg:)`, which is in the API schema. + "###); +} + +#[test] +fn removes_inaccessible_input_object_types() { + let api_schema = inaccessible_to_api_schema( + r#" + type Query { + someField: String + } + + # Non-inaccessible input object type + input VisibleInputObject { + someField: String + } + + # Inaccessible input object type + input InputObject @inaccessible { + someField: String + } + + # Inaccessible input object type referenced by inaccessible object field + # argument + type Referencer1 implements Referencer4 { + someField(privateArg: InputObject @inaccessible): String + } + + # Inaccessible input object type referenced by non-inaccessible object + # field argument with inaccessible parent + type Referencer2 implements Referencer5 { + someField: String + privateField(privateArg: InputObject!): String @inaccessible + } + + # Inaccessible input object type referenced by non-inaccessible object + # field argument with inaccessible grandparent + type Referencer3 implements Referencer6 @inaccessible { + privateField(privateArg: InputObject!): String + } + + # Inaccessible input object type referenced by inaccessible interface + # field argument + interface Referencer4 { + someField(privateArg: InputObject @inaccessible): String + } + + # Inaccessible input object type referenced by non-inaccessible interface + # field argument with inaccessible parent + interface Referencer5 { + someField: String + privateField(privateArg: InputObject!): String @inaccessible + } + + # Inaccessible input object type referenced by non-inaccessible interface + # field argument with inaccessible grandparent + interface Referencer6 @inaccessible { + privateField(privateArg: InputObject!): String + } + + # Inaccessible input object type referenced by inaccessible input object + # field + input Referencer7 { + someField: String + privateField: InputObject @inaccessible + } + + # Inaccessible input object type referenced by non-inaccessible input + # object field with inaccessible parent + input Referencer8 @inaccessible { + privateField: InputObject! + } + + # Inaccessible input object type referenced by inaccessible directive + # argument + directive @referencer9(privateArg: InputObject @inaccessible) on FIELD + "#, + ) + .expect("should succeed"); + + assert!(api_schema.types.contains_key("VisibleInputObject")); + assert!(!api_schema.types.contains_key("InputObject")); + assert!(coord!(Referencer1.someField).lookup(&api_schema).is_ok()); + assert!(coord!(Referencer1.someField(privateArg:)) + .lookup(&api_schema) + .is_err()); + assert!(coord!(Referencer2.someField).lookup(&api_schema).is_ok()); + assert!(coord!(Referencer2.privateField) + .lookup(&api_schema) + .is_err()); + assert!(!api_schema.types.contains_key("Referencer3")); + assert!(coord!(Referencer4.someField).lookup(&api_schema).is_ok()); + assert!(coord!(Referencer4.someField(privateArg:)) + .lookup(&api_schema) + .is_err()); + assert!(coord!(Referencer4.someField).lookup(&api_schema).is_ok()); + assert!(coord!(Referencer4.privatefield) + .lookup(&api_schema) + .is_err()); + assert!(coord!(Referencer5.someField).lookup(&api_schema).is_ok()); + assert!(coord!(Referencer5.privateField) + .lookup(&api_schema) + .is_err()); + assert!(!api_schema.types.contains_key("Referencer6")); + let ExtendedType::InputObject(input_object) = &api_schema.types["Referencer7"] else { + panic!("expected input object"); + }; + assert!(input_object.fields.contains_key("someField")); + assert!(!input_object.fields.contains_key("privatefield")); + assert!(!api_schema.types.contains_key("Referencer8")); + assert!(coord!(@referencer9).lookup(&api_schema).is_ok()); + assert!(coord!(@referencer9(privateArg:)) + .lookup(&api_schema) + .is_err()); +} + +#[test] +fn inaccessible_enum_with_accessible_references() { + let errors = inaccessible_to_api_schema( + r#" + type Query { + someField: String + } + + # Inaccessible enum type + enum Enum @inaccessible { + SOME_VALUE + } + + # Inaccessible enum type can't be referenced by object field in the API + # schema + type Referencer1 implements Referencer2 { + somefield: [Enum!]! + } + + # Inaccessible enum type can't be referenced by interface field in the API + # schema + interface Referencer2 { + somefield: [Enum] + } + + # Inaccessible enum type can't be referenced by object field argument in + # the API schema + type Referencer3 implements Referencer4 { + someField(someArg: Enum): String + } + + # Inaccessible enum type can't be referenced by interface field argument + # in the API schema + interface Referencer4 { + someField(someArg: Enum): String + } + + # Inaccessible enum type can't be referenced by input object field in the + # API schema + input Referencer5 { + someField: Enum + } + + # Inaccessible enum type can't be referenced by directive argument in the + # API schema + directive @referencer6(someArg: Enum) on FRAGMENT_SPREAD + "#, + ) + .expect_err("should return validation errors"); + + insta::assert_snapshot!(errors, @r###" + The following errors occurred: + + - Type `Enum` is @inaccessible but is referenced by `Referencer1.somefield`, which is in the API schema. + + - Type `Enum` is @inaccessible but is referenced by `Referencer2.somefield`, which is in the API schema. + + - Type `Enum` is @inaccessible but is referenced by `Referencer5.someField`, which is in the API schema. + + - Type `Enum` is @inaccessible but is referenced by `Referencer3.someField(someArg:)`, which is in the API schema. + + - Type `Enum` is @inaccessible but is referenced by `Referencer4.someField(someArg:)`, which is in the API schema. + + - Type `Enum` is @inaccessible but is referenced by `@referencer6(someArg:)`, which is in the API schema. + "###); +} + +#[test] +fn removes_inaccessible_enum_types() { + let api_schema = inaccessible_to_api_schema( + r#" + type Query { + someField: String + } + + # Non-inaccessible enum type + enum VisibleEnum { + SOME_VALUE + } + + # Inaccessible enum type + enum Enum @inaccessible { + SOME_VALUE + } + + # Inaccessible enum type referenced by inaccessible object field + type Referencer1 implements Referencer3 { + someField: String + privatefield: Enum! @inaccessible + } + + # Inaccessible enum type referenced by non-inaccessible object field with + # inaccessible parent + type Referencer2 implements Referencer4 @inaccessible { + privateField: [Enum!]! + } + + # Inaccessible enum type referenced by inaccessible interface field + interface Referencer3 { + someField: String + privatefield: Enum @inaccessible + } + + # Inaccessible enum type referenced by non-inaccessible interface field + # with inaccessible parent + interface Referencer4 @inaccessible { + privateField: [Enum] + } + + # Inaccessible enum type referenced by inaccessible object field argument + type Referencer5 implements Referencer8 { + someField(privateArg: Enum @inaccessible): String + } + + # Inaccessible enum type referenced by non-inaccessible object field + # argument with inaccessible parent + type Referencer6 implements Referencer9 { + someField: String + privateField(privateArg: Enum!): String @inaccessible + } + + # Inaccessible enum type referenced by non-inaccessible object field + # argument with inaccessible grandparent + type Referencer7 implements Referencer10 @inaccessible { + privateField(privateArg: Enum!): String + } + + # Inaccessible enum type referenced by inaccessible interface field + # argument + interface Referencer8 { + someField(privateArg: Enum @inaccessible): String + } + + # Inaccessible enum type referenced by non-inaccessible interface field + # argument with inaccessible parent + interface Referencer9 { + someField: String + privateField(privateArg: Enum!): String @inaccessible + } + + # Inaccessible enum type referenced by non-inaccessible interface field + # argument with inaccessible grandparent + interface Referencer10 @inaccessible { + privateField(privateArg: Enum!): String + } + + # Inaccessible enum type referenced by inaccessible input object field + input Referencer11 { + someField: String + privateField: Enum @inaccessible + } + + # Inaccessible enum type referenced by non-inaccessible input object field + # with inaccessible parent + input Referencer12 @inaccessible { + privateField: Enum! + } + + # Inaccessible enum type referenced by inaccessible directive argument + directive @referencer13(privateArg: Enum @inaccessible) on FRAGMENT_DEFINITION + "#, + ) + .expect("should succeed"); + + assert!(api_schema.types.contains_key("VisibleEnum")); + assert!(!api_schema.types.contains_key("Enum")); + assert!(coord!(Referencer1.someField).lookup(&api_schema).is_ok()); + assert!(coord!(Referencer1.privatefield) + .lookup(&api_schema) + .is_err()); + assert!(coord!(Referencer1.someField(privateArg:)) + .lookup(&api_schema) + .is_err()); + assert!(!api_schema.types.contains_key("Referencer2")); + assert!(coord!(Referencer3.someField).lookup(&api_schema).is_ok()); + assert!(coord!(Referencer3.privatefield) + .lookup(&api_schema) + .is_err()); + assert!(!api_schema.types.contains_key("Referencer4")); + assert!(coord!(Referencer5.someField).lookup(&api_schema).is_ok()); + assert!(coord!(Referencer5.someField(privateArg:)) + .lookup(&api_schema) + .is_err()); + assert!(coord!(Referencer6.someField).lookup(&api_schema).is_ok()); + assert!(coord!(Referencer6.privateField) + .lookup(&api_schema) + .is_err()); + assert!(!api_schema.types.contains_key("Referencer7")); + assert!(coord!(Referencer8.someField).lookup(&api_schema).is_ok()); + assert!(coord!(Referencer8.someField(privateArg:)) + .lookup(&api_schema) + .is_err()); + assert!(coord!(Referencer9.privateField) + .lookup(&api_schema) + .is_err()); + assert!(!api_schema.types.contains_key("Referencer10")); + let ExtendedType::InputObject(input_object) = &api_schema.types["Referencer11"] else { + panic!("expected input object"); + }; + assert!(input_object.fields.contains_key("someField")); + assert!(!input_object.fields.contains_key("privatefield")); + assert!(!api_schema.types.contains_key("Referencer12")); + assert!(api_schema + .directive_definitions + .contains_key("referencer13")); + assert!(coord!(@referencer13(privateArg:)) + .lookup(&api_schema) + .is_err()); +} + +#[test] +fn inaccessible_scalar_with_accessible_references() { + let errors = inaccessible_to_api_schema( + r#" + type Query { + someField: String + } + + # Inaccessible scalar type + scalar Scalar @inaccessible + + # Inaccessible scalar type can't be referenced by object field in the API + # schema + type Referencer1 implements Referencer2 { + somefield: [[Scalar!]!]! + } + + # Inaccessible scalar type can't be referenced by interface field in the + # API schema + interface Referencer2 { + somefield: [[Scalar]] + } + + # Inaccessible scalar type can't be referenced by object field argument in + # the API schema + type Referencer3 implements Referencer4 { + someField(someArg: Scalar): String + } + + # Inaccessible scalar type can't be referenced by interface field argument + # in the API schema + interface Referencer4 { + someField(someArg: Scalar): String + } + + # Inaccessible scalar type can't be referenced by input object field in + # the API schema + input Referencer5 { + someField: Scalar + } + + # Inaccessible scalar type can't be referenced by directive argument in + # the API schema + directive @referencer6(someArg: Scalar) on MUTATION + "#, + ) + .expect_err("should return validation errors"); + + insta::assert_snapshot!(errors, @r###" + The following errors occurred: + + - Type `Scalar` is @inaccessible but is referenced by `Referencer1.somefield`, which is in the API schema. + + - Type `Scalar` is @inaccessible but is referenced by `Referencer2.somefield`, which is in the API schema. + + - Type `Scalar` is @inaccessible but is referenced by `Referencer5.someField`, which is in the API schema. + + - Type `Scalar` is @inaccessible but is referenced by `Referencer3.someField(someArg:)`, which is in the API schema. + + - Type `Scalar` is @inaccessible but is referenced by `Referencer4.someField(someArg:)`, which is in the API schema. + + - Type `Scalar` is @inaccessible but is referenced by `@referencer6(someArg:)`, which is in the API schema. + "###); +} + +#[test] +fn removes_inaccessible_scalar_types() { + let api_schema = inaccessible_to_api_schema( + r#" + type Query { + someField: String + } + + # Non-inaccessible scalar type + scalar VisibleScalar + + # Inaccessible scalar type + scalar Scalar @inaccessible + + # Inaccessible scalar type referenced by inaccessible object field + type Referencer1 implements Referencer3 { + someField: String + privatefield: Scalar! @inaccessible + } + + # Inaccessible scalar type referenced by non-inaccessible object field + # with inaccessible parent + type Referencer2 implements Referencer4 @inaccessible { + privateField: [Scalar!]! + } + + # Inaccessible scalar type referenced by inaccessible interface field + interface Referencer3 { + someField: String + privatefield: Scalar @inaccessible + } + + # Inaccessible scalar type referenced by non-inaccessible interface field + # with inaccessible parent + interface Referencer4 @inaccessible { + privateField: [Scalar] + } + + # Inaccessible scalar type referenced by inaccessible object field + # argument + type Referencer5 implements Referencer8 { + someField(privateArg: Scalar @inaccessible): String + } + + # Inaccessible scalar type referenced by non-inaccessible object field + # argument with inaccessible parent + type Referencer6 implements Referencer9 { + someField: String + privateField(privateArg: Scalar!): String @inaccessible + } + + # Inaccessible scalar type referenced by non-inaccessible object field + # argument with inaccessible grandparent + type Referencer7 implements Referencer10 @inaccessible { + privateField(privateArg: Scalar!): String + } + + # Inaccessible scalar type referenced by inaccessible interface field + # argument + interface Referencer8 { + someField(privateArg: Scalar @inaccessible): String + } + + # Inaccessible scalar type referenced by non-inaccessible interface field + # argument with inaccessible parent + interface Referencer9 { + someField: String + privateField(privateArg: Scalar!): String @inaccessible + } + + # Inaccessible scalar type referenced by non-inaccessible interface field + # argument with inaccessible grandparent + interface Referencer10 @inaccessible { + privateField(privateArg: Scalar!): String + } + + # Inaccessible scalar type referenced by inaccessible input object field + input Referencer11 { + someField: String + privateField: Scalar @inaccessible + } + + # Inaccessible scalar type referenced by non-inaccessible input object + # field with inaccessible parent + input Referencer12 @inaccessible { + privateField: Scalar! + } + + # Inaccessible scalar type referenced by inaccessible directive argument + directive @referencer13(privateArg: Scalar @inaccessible) on INLINE_FRAGMENT + "#, + ) + .expect("should succeed"); + + assert!(coord!(VisibleScalar).lookup(&api_schema).is_ok()); + assert!(coord!(Scalar).lookup(&api_schema).is_err()); + assert!(coord!(Referencer1.someField).lookup(&api_schema).is_ok()); + assert!(coord!(Referencer1.privatefield) + .lookup(&api_schema) + .is_err()); + assert!(coord!(Referencer2).lookup(&api_schema).is_err()); + assert!(coord!(Referencer3.someField).lookup(&api_schema).is_ok()); + assert!(coord!(Referencer3.privatefield) + .lookup(&api_schema) + .is_err()); + assert!(coord!(Referencer4).lookup(&api_schema).is_err()); + assert!(coord!(Referencer5.someField).lookup(&api_schema).is_ok()); + assert!(coord!(Referencer5.someField(privateArg:)) + .lookup(&api_schema) + .is_err()); + assert!(coord!(Referencer6.someField).lookup(&api_schema).is_ok()); + assert!(coord!(Referencer6.privateField) + .lookup(&api_schema) + .is_err()); + assert!(coord!(Referencer7).lookup(&api_schema).is_err()); + assert!(coord!(Referencer8.someField).lookup(&api_schema).is_ok()); + assert!(coord!(Referencer8.someField(privateArg:)) + .lookup(&api_schema) + .is_err()); + assert!(coord!(Referencer9.someField).lookup(&api_schema).is_ok()); + assert!(coord!(Referencer9.privateField) + .lookup(&api_schema) + .is_err()); + assert!(coord!(Referencer10).lookup(&api_schema).is_err()); + assert!(coord!(Referencer11).lookup(&api_schema).is_ok()); + assert!(coord!(Referencer11.someField).lookup(&api_schema).is_ok()); + assert!(coord!(Referencer11.privatefield) + .lookup(&api_schema) + .is_err()); + assert!(coord!(Referencer12).lookup(&api_schema).is_err()); + assert!(coord!(@referencer13).lookup(&api_schema).is_ok()); + assert!(coord!(@referencer13(privateArg:)) + .lookup(&api_schema) + .is_err()); +} + +#[test] +fn inaccessible_object_field_with_accessible_references() { + let errors = inaccessible_to_api_schema( + r#" + extend schema { + mutation: Mutation + subscription: Subscription + } + + # Inaccessible object field can't have a non-inaccessible parent query + # type and no non-inaccessible siblings + type Query { + privateField: String @inaccessible + otherPrivateField: Float @inaccessible + } + + # Inaccessible object field can't have a non-inaccessible parent mutation + # type and no non-inaccessible siblings + type Mutation { + privateField: String @inaccessible + otherPrivateField: Float @inaccessible + } + + # Inaccessible object field can't have a non-inaccessible parent + # subscription type and no non-inaccessible siblings + type Subscription { + privateField: String @inaccessible + otherPrivateField: Float @inaccessible + } + + # Inaccessible object field + type Object implements Referencer1 { + someField: String + privateField: String @inaccessible + } + + # Inaccessible object field can't be referenced by interface field in the + # API schema + interface Referencer1 { + privateField: String + } + + # Inaccessible object field can't have a non-inaccessible parent object + # type and no non-inaccessible siblings + type Referencer2 { + privateField: String @inaccessible + otherPrivateField: Float @inaccessible + } + "#, + ) + .expect_err("should return validation errors"); + + insta::assert_snapshot!(errors, @r###" + The following errors occurred: + + - Type `Query` is in the API schema but all of its members are @inaccessible. + + - Type `Mutation` is in the API schema but all of its members are @inaccessible. + + - Type `Subscription` is in the API schema but all of its members are @inaccessible. + + - Field `Object.privateField` is @inaccessible but implements the interface field `Referencer1.privateField`, which is in the API schema. + + - Type `Referencer2` is in the API schema but all of its members are @inaccessible. + "###); +} + +#[test] +fn removes_inaccessible_object_fields() { + let api_schema = inaccessible_to_api_schema( + r#" + extend schema { + mutation: Mutation + subscription: Subscription + } + + # Inaccessible object field on query type + type Query { + someField: String + privateField: String @inaccessible + } + + # Inaccessible object field on mutation type + type Mutation { + someField: String + privateField: String @inaccessible + } + + # Inaccessible object field on subscription type + type Subscription { + someField: String + privateField: String @inaccessible + } + + # Inaccessible (and non-inaccessible) object field + type Object implements Referencer1 & Referencer2 { + someField: String + privateField: String @inaccessible + } + + # Inaccessible object field referenced by inaccessible interface field + interface Referencer1 { + someField: String + privateField: String @inaccessible + } + + # Inaccessible object field referenced by non-inaccessible interface field + # with inaccessible parent + interface Referencer2 @inaccessible { + privateField: String + } + + # Inaccessible object field with an inaccessible parent and no + # non-inaccessible siblings + type Referencer3 @inaccessible { + privateField: String @inaccessible + otherPrivateField: Float @inaccessible + } + "#, + ) + .expect("should succeed"); + + assert!(coord!(Query.someField).lookup(&api_schema).is_ok()); + assert!(coord!(Query.privateField).lookup(&api_schema).is_err()); + assert!(coord!(Mutation.someField).lookup(&api_schema).is_ok()); + assert!(coord!(Mutation.privateField).lookup(&api_schema).is_err()); + assert!(coord!(Subscription.someField).lookup(&api_schema).is_ok()); + assert!(coord!(Subscription.privateField) + .lookup(&api_schema) + .is_err()); + let ExtendedType::Object(object_type) = &api_schema.types["Object"] else { + panic!("should be object"); + }; + assert!(object_type.implements_interfaces.contains("Referencer1")); + assert!(!object_type.implements_interfaces.contains("Referencer2")); + assert!(coord!(Object.someField).lookup(&api_schema).is_ok()); + assert!(coord!(Object.privateField).lookup(&api_schema).is_err()); + assert!(coord!(Referencer1.someField).lookup(&api_schema).is_ok()); + assert!(coord!(Referencer1.privatefield) + .lookup(&api_schema) + .is_err()); + assert!(api_schema.types.get("Referencer2").is_none()); + assert!(api_schema.types.get("Referencer3").is_none()); +} + +#[test] +fn inaccessible_interface_field_with_accessible_references() { + let errors = inaccessible_to_api_schema( + r#" + type Query { + someField: String + } + + # Inaccessible interface field + interface Interface implements Referencer1 { + someField: String + privateField: String @inaccessible + } + + # Inaccessible interface field can't be referenced by interface field in + # the API schema + interface Referencer1 { + privateField: String + } + + # Inaccessible interface field can't have a non-inaccessible parent object + # type and no non-inaccessible siblings + interface Referencer2 { + privateField: String @inaccessible + otherPrivateField: Float @inaccessible + } + "#, + ) + .expect_err("should return validation errors"); + + insta::assert_snapshot!(errors, @r###" + The following errors occurred: + + - Field `Interface.privateField` is @inaccessible but implements the interface field `Referencer1.privateField`, which is in the API schema. + + - Type `Referencer2` is in the API schema but all of its members are @inaccessible. + "###); +} + +#[test] +fn removes_inaccessible_interface_fields() { + let api_schema = inaccessible_to_api_schema( + r#" + type Query { + someField: String + } + + # Inaccessible (and non-inaccessible) interface field + interface Interface implements Referencer1 & Referencer2 { + someField: String + privateField: String @inaccessible + } + + # Inaccessible interface field referenced by inaccessible interface field + interface Referencer1 { + someField: String + privateField: String @inaccessible + } + + # Inaccessible interface field referenced by non-inaccessible interface + # field with inaccessible parent + interface Referencer2 @inaccessible { + privateField: String + } + + # Inaccessible interface field with an inaccessible parent and no + # non-inaccessible siblings + interface Referencer3 @inaccessible { + privateField: String @inaccessible + otherPrivateField: Float @inaccessible + } + "#, + ) + .expect("should succeed"); + + let ExtendedType::Interface(interface_type) = &api_schema.types["Interface"] else { + panic!("should be interface"); + }; + assert!(interface_type.implements_interfaces.contains("Referencer1")); + assert!(!interface_type.implements_interfaces.contains("Referencer2")); + assert!(coord!(Interface.someField).lookup(&api_schema).is_ok()); + assert!(coord!(Interface.privateField).lookup(&api_schema).is_err()); + assert!(coord!(Referencer1.someField).lookup(&api_schema).is_ok()); + assert!(coord!(Referencer1.privatefield) + .lookup(&api_schema) + .is_err()); + assert!(api_schema.types.get("Referencer2").is_none()); + assert!(api_schema.types.get("Referencer3").is_none()); +} + +#[test] +fn inaccessible_object_field_arguments_with_accessible_references() { + let errors = inaccessible_to_api_schema( + r#" + type Query { + someField(someArg: String): String + } + + # Inaccessible object field argument + type Object implements Referencer1 { + someField(privateArg: String @inaccessible): String + } + + # Inaccessible object field argument can't be referenced by interface + # field argument in the API schema + interface Referencer1 { + someField(privateArg: String): String + } + + # Inaccessible object field argument can't be a required argument + type ObjectRequired { + someField(privateArg: String! @inaccessible): String + } + "#, + ) + .expect_err("should return validation errors"); + + insta::assert_snapshot!(errors, @r###" + The following errors occurred: + + - Argument `Object.someField(privateArg:)` is @inaccessible but implements the interface argument `Referencer1.someField(privateArg:)` which is in the API schema. + + - Argument `ObjectRequired.someField(privateArg:)` is @inaccessible but is a required argument of its field. + "###); +} + +#[test] +fn removes_inaccessible_object_field_arguments() { + let api_schema = inaccessible_to_api_schema( + r#" + # Inaccessible object field argument in query type + type Query { + someField(privateArg: String @inaccessible): String + } + + # Inaccessible object field argument in mutation type + type Mutation { + someField(privateArg: String @inaccessible): String + } + + # Inaccessible object field argument in subscription type + type Subscription { + someField(privateArg: String @inaccessible): String + } + + # Inaccessible (and non-inaccessible) object field argument + type Object implements Referencer1 & Referencer2 & Referencer3 { + someField( + someArg: String, + privateArg: String @inaccessible + ): String + someOtherField: Float + } + + # Inaccessible object field argument referenced by inaccessible interface + # field argument + interface Referencer1 { + someField( + someArg: String, + privateArg: String @inaccessible + ): String + } + + # Inaccessible object field argument referenced by non-inaccessible + # interface field argument with inaccessible parent + interface Referencer2 { + someField( + someArg: String, + privateArg: String + ): String @inaccessible + someOtherField: Float + } + + # Inaccessible object field argument referenced by non-inaccessible + # interface field argument with inaccessible grandparent + interface Referencer3 @inaccessible { + someField( + someArg: String, + privateArg: String + ): String + } + + # Inaccessible non-nullable object field argument with default + type ObjectDefault { + someField(privateArg: String! = "default" @inaccessible): String + } + "#, + ) + .expect("should succeed"); + + assert!(coord!(Query.someField).lookup(&api_schema).is_ok()); + assert!(coord!(Query.someField(privateArg:)) + .lookup(&api_schema) + .is_err()); + assert!(coord!(Mutation.someField).lookup(&api_schema).is_ok()); + assert!(coord!(Mutation.someField(privateArg:)) + .lookup(&api_schema) + .is_err()); + assert!(coord!(Subscription.someField).lookup(&api_schema).is_ok()); + assert!(coord!(Subscription.someField(privateArg:)) + .lookup(&api_schema) + .is_err()); + let ExtendedType::Object(object_type) = &api_schema.types["Object"] else { + panic!("expected object"); + }; + assert!(object_type.implements_interfaces.contains("Referencer1")); + assert!(object_type.implements_interfaces.contains("Referencer2")); + assert!(!object_type.implements_interfaces.contains("Referencer3")); + assert!(coord!(Object.someField(someArg:)) + .lookup(&api_schema) + .is_ok()); + assert!(coord!(Object.someField(privateArg:)) + .lookup(&api_schema) + .is_err()); + assert!(coord!(Referencer1.someField).lookup(&api_schema).is_ok()); + assert!(coord!(Referencer1.someField(privateArg:)) + .lookup(&api_schema) + .is_err()); + assert!(coord!(Referencer2).lookup(&api_schema).is_ok()); + assert!(coord!(Referencer2.someField).lookup(&api_schema).is_err()); + assert!(!api_schema.types.contains_key("Referencer3")); + assert!(coord!(ObjectDefault.someField).lookup(&api_schema).is_ok()); + assert!(coord!(ObjectDefault.someField(privateArg:)) + .lookup(&api_schema) + .is_err()); +} + +#[test] +fn inaccessible_interface_field_arguments_with_accessible_references() { + let errors = inaccessible_to_api_schema( + r#" + type Query { + someField(someArg: String): String + } + + # Inaccessible interface field argument + interface Interface implements Referencer1 { + someField(privateArg: String! = "default" @inaccessible): String + } + + # Inaccessible interface field argument can't be referenced by interface + # field argument in the API schema + interface Referencer1 { + someField(privateArg: String! = "default"): String + } + + # Inaccessible object field argument can't be a required argument + type InterfaceRequired { + someField(privateArg: String! @inaccessible): String + } + + # Inaccessible object field argument can't be implemented by a required + # object field argument in the API schema + type Referencer2 implements Interface & Referencer1 { + someField(privateArg: String!): String + } + + # Inaccessible object field argument can't be implemented by a required + # interface field argument in the API schema + interface Referencer3 implements Interface & Referencer1 { + someField(privateArg: String!): String + } + "#, + ) + .expect_err("should return validation errors"); + + insta::assert_snapshot!(errors, @r###" + The following errors occurred: + + - Argument `Interface.someField(privateArg:)` is @inaccessible but implements the interface argument `Referencer1.someField(privateArg:)` which is in the API schema. + + - Argument `InterfaceRequired.someField(privateArg:)` is @inaccessible but is a required argument of its field. + + - Argument `Interface.someField(privateArg:)` is @inaccessible but is implemented by the argument `Referencer2.someField(privateArg:)` which is in the API schema. + + - Argument `Interface.someField(privateArg:)` is @inaccessible but is implemented by the argument `Referencer3.someField(privateArg:)` which is in the API schema. + "###); +} + +#[test] +fn removes_inaccessible_interface_field_arguments() { + let api_schema = inaccessible_to_api_schema( + r#" + type Query { + someField: String + } + + # Inaccessible (and non-inaccessible) interface field argument + interface Interface implements Referencer1 & Referencer2 & Referencer3 { + someField( + someArg: String, + privateArg: String @inaccessible + ): String + someOtherField: Float + } + + # Inaccessible interface field argument referenced by inaccessible + # interface field argument + interface Referencer1 { + someField( + someArg: String, + privateArg: String @inaccessible + ): String + } + + # Inaccessible interface field argument referenced by non-inaccessible + # interface field argument with inaccessible parent + interface Referencer2 { + someField( + someArg: String, + privateArg: String + ): String @inaccessible + someOtherField: Float + } + + # Inaccessible interface field argument referenced by non-inaccessible + # interface field argument with inaccessible grandparent + interface Referencer3 @inaccessible { + someField( + someArg: String, + privateArg: String + ): String + } + + # Inaccessible non-nullable interface field argument with default + interface InterfaceDefault { + someField(privateArg: String! = "default" @inaccessible): String + } + + # Inaccessible interface field argument referenced by non-inaccessible + # non-required object field argument + type Referencer4 implements InterfaceDefault { + someField(privateArg: String! = "default"): String + } + + # Inaccessible interface field argument referenced by non-inaccessible + # required object field argument with inaccessible grandparent + type Referencer5 implements InterfaceDefault @inaccessible { + someField(privateArg: String!): String + } + + # Inaccessible interface field argument referenced by non-inaccessible + # non-required interface field argument + interface Referencer6 implements InterfaceDefault { + someField(privateArg: String! = "default"): String + } + + # Inaccessible interface field argument referenced by non-inaccessible + # required interface field argument with inaccessible grandparent + interface Referencer7 implements InterfaceDefault @inaccessible { + someField(privateArg: String!): String + } + "#, + ) + .expect("should succeed"); + + let ExtendedType::Interface(interface_type) = &api_schema.types["Interface"] else { + panic!("expected interface"); + }; + assert!(interface_type.implements_interfaces.contains("Referencer1")); + assert!(interface_type.implements_interfaces.contains("Referencer2")); + assert!(!interface_type.implements_interfaces.contains("Referencer3")); + assert!(coord!(Interface.someField(someArg:)) + .lookup(&api_schema) + .is_ok()); + assert!(coord!(Interface.someField(privateArg:)) + .lookup(&api_schema) + .is_err()); + assert!(coord!(Referencer1.someField).lookup(&api_schema).is_ok()); + assert!(coord!(Referencer1.someField(privateArg:)) + .lookup(&api_schema) + .is_err()); + assert!(api_schema.types.contains_key("Referencer2")); + assert!(coord!(Referencer2.someField).lookup(&api_schema).is_err()); + assert!(!api_schema.types.contains_key("Referencer3")); + assert!(coord!(Interface.someField(privateArg:)) + .lookup(&api_schema) + .is_err()); + let object_argument = coord!(Referencer4.someField(privateArg:)) + .lookup(&api_schema) + .unwrap(); + assert!(!object_argument.is_required()); + assert!(!api_schema.types.contains_key("Referencer5")); + let interface_argument = coord!(Referencer4.someField(privateArg:)) + .lookup(&api_schema) + .unwrap(); + assert!(!interface_argument.is_required()); + assert!(!api_schema.types.contains_key("Referencer7")); +} + +#[test] +fn inaccessible_input_object_fields_with_accessible_references() { + let errors = inaccessible_to_api_schema( + r#" + type Query { + someField: String + } + + # Inaccessible input object field + input InputObject { + someField: String + privateField: String @inaccessible + } + + # Inaccessible input object field can't be referenced by default value of + # object field argument in the API schema + type Referencer1 implements Referencer2 { + someField(someArg: InputObject = { privateField: "" }): String + } + + # Inaccessible input object field can't be referenced by default value of + # interface field argument in the API schema + interface Referencer2 { + someField(someArg: InputObject = { privateField: "" }): String + } + + # Inaccessible input object field can't be referenced by default value of + # input object field in the API schema + input Referencer3 { + someField: InputObject = { privateField: "" } + } + + # Inaccessible input object field can't be referenced by default value of + # directive argument in the API schema + directive @referencer4( + someArg: InputObject = { privateField: "" } + ) on FIELD + + # Inaccessible input object field can't have a non-inaccessible parent + # and no non-inaccessible siblings + input Referencer5 { + privateField: String @inaccessible + otherPrivateField: Float @inaccessible + } + + # Inaccessible input object field can't be a required field + input InputObjectRequired { + someField: String + privateField: String! @inaccessible + } + "#, + ) + .expect_err("should return validation errors"); + + insta::assert_snapshot!(errors, @r###" + The following errors occurred: + + - Input field `InputObject.privateField` is @inaccessible but is used in the default value of `Referencer1.someField(someArg:)`, which is in the API schema. + + - Input field `InputObject.privateField` is @inaccessible but is used in the default value of `Referencer2.someField(someArg:)`, which is in the API schema. + + - Input field `InputObject.privateField` is @inaccessible but is used in the default value of `Referencer3.someField`, which is in the API schema. + + - Type `Referencer5` is in the API schema but all of its input fields are @inaccessible. + + - Input field `InputObjectRequired` is @inaccessible but is a required input field of its type. + + - Input field `InputObject.privateField` is @inaccessible but is used in the default value of `@referencer4(someArg:)`, which is in the API schema. + "###); +} + +#[test] +fn removes_inaccessible_input_object_fields() { + let schema = inaccessible_to_api_schema( + r#" + type Query { + someField: String + } + + # Inaccessible (and non-inaccessible) input object field + input InputObject { + someField: String + privateField: String @inaccessible + } + + # Inaccessible input object field referenced by default value of + # inaccessible object field argument + type Referencer1 implements Referencer4 { + someField( + privateArg: InputObject = { privateField: "" } @inaccessible + ): String + } + + # Inaccessible input object field referenced by default value of + # non-inaccessible object field argument with inaccessible parent + type Referencer2 implements Referencer5 { + someField: String + privateField( + privateArg: InputObject! = { privateField: "" } + ): String @inaccessible + } + + # Inaccessible input object field referenced by default value of + # non-inaccessible object field argument with inaccessible grandparent + type Referencer3 implements Referencer6 @inaccessible { + privateField(privateArg: InputObject! = { privateField: "" }): String + } + + # Inaccessible input object field referenced by default value of + # inaccessible interface field argument + interface Referencer4 { + someField( + privateArg: InputObject = { privateField: "" } @inaccessible + ): String + } + + # Inaccessible input object field referenced by default value of + # non-inaccessible interface field argument with inaccessible parent + interface Referencer5 { + someField: String + privateField( + privateArg: InputObject! = { privateField: "" } + ): String @inaccessible + } + + # Inaccessible input object field referenced by default value of + # non-inaccessible interface field argument with inaccessible grandparent + interface Referencer6 @inaccessible { + privateField(privateArg: InputObject! = { privateField: "" }): String + } + + # Inaccessible input object field referenced by default value of + # inaccessible input object field + input Referencer7 { + someField: String + privateField: InputObject = { privateField: "" } @inaccessible + } + + # Inaccessible input object field referenced by default value of + # non-inaccessible input object field with inaccessible parent + input Referencer8 @inaccessible { + privateField: InputObject! = { privateField: "" } + } + + # Inaccessible input object field referenced by default value of + # inaccessible directive argument + directive @referencer9( + privateArg: InputObject = { privateField: "" } @inaccessible + ) on SUBSCRIPTION + + # Inaccessible input object field not referenced (but type is referenced) + # by default value of object field argument in the API schema + type Referencer10 { + someField(privateArg: InputObject = { someField: "" }): String + } + + # Inaccessible input object field with an inaccessible parent and no + # non-inaccessible siblings + input Referencer11 @inaccessible { + privateField: String @inaccessible + otherPrivateField: Float @inaccessible + } + + # Inaccessible non-nullable input object field with default + input InputObjectDefault { + someField: String + privateField: String! = "default" @inaccessible + } + "#, + ) + .expect("should succeed"); + + assert!(coord!(InputObject.someField) + .lookup_input_field(&schema) + .is_ok()); + assert!(coord!(InputObject.privateField) + .lookup_input_field(&schema) + .is_err()); + assert!(coord!(Referencer1.someField).lookup(&schema).is_ok()); + assert!(coord!(Referencer1.someField(privateArg:)) + .lookup(&schema) + .is_err()); + assert!(coord!(Referencer2.someField).lookup(&schema).is_ok()); + assert!(coord!(Referencer2.privateField).lookup(&schema).is_err()); + assert!(!schema.types.contains_key("Referencer3")); + assert!(coord!(Referencer4.someField).lookup(&schema).is_ok()); + assert!(coord!(Referencer4.someField(privateArg:)) + .lookup(&schema) + .is_err()); + assert!(coord!(Referencer5.someField).lookup(&schema).is_ok()); + assert!(coord!(Referencer5.privateField).lookup(&schema).is_err()); + assert!(!schema.types.contains_key("Referencer6")); + assert!(schema + .get_input_object("Referencer7") + .unwrap() + .fields + .contains_key("someField")); + assert!(!schema + .get_input_object("Referencer7") + .unwrap() + .fields + .contains_key("privatefield")); + assert!(!schema.types.contains_key("Referencer8")); + assert!(schema.directive_definitions.contains_key("referencer9")); + assert!(coord!(@referencer9(privateArg:)).lookup(&schema).is_err()); + assert!(coord!(Referencer10.someField(privateArg:)) + .lookup(&schema) + .is_ok()); + assert!(!schema.types.contains_key("Referencer11")); + assert!(coord!(InputObjectDefault.someField) + .lookup_input_field(&schema) + .is_ok()); + assert!(coord!(InputObjectDefault.privatefield) + .lookup_input_field(&schema) + .is_err()); +} + +#[test] +fn inaccessible_enum_values_with_accessible_references() { + let errors = inaccessible_to_api_schema( + r#" + type Query { + someField: String + } + + # Inaccessible enum value + enum Enum { + SOME_VALUE + PRIVATE_VALUE @inaccessible + } + + # Inaccessible enum value can't be referenced by default value of object + # field argument in the API schema + type Referencer1 implements Referencer2 { + someField(someArg: Enum = PRIVATE_VALUE): String + } + + # Inaccessible enum value can't be referenced by default value of + # interface field argument in the API schema + interface Referencer2 { + someField(someArg: Enum = PRIVATE_VALUE): String + } + + # Inaccessible enum value can't be referenced by default value of input + # object field in the API schema + input Referencer3 { + someField: Enum = PRIVATE_VALUE + } + + # Inaccessible input enum value can't be referenced by default value of + # directive argument in the API schema + directive @referencer4(someArg: Enum = PRIVATE_VALUE) on INLINE_FRAGMENT + + # Inaccessible enum value can't have a non-inaccessible parent and no + # non-inaccessible siblings + enum Referencer5 { + PRIVATE_VALUE @inaccessible + OTHER_PRIVATE_VALUE @inaccessible + } + "#, + ) + .expect_err("should return validation errors"); + + insta::assert_snapshot!(errors, @r###" + The following errors occurred: + + - Enum value `Enum.PRIVATE_VALUE` is @inaccessible but is used in the default value of `Referencer1.someField(someArg:)`, which is in the API schema. + + - Enum value `Enum.PRIVATE_VALUE` is @inaccessible but is used in the default value of `Referencer2.someField(someArg:)`, which is in the API schema. + + - Enum value `Enum.PRIVATE_VALUE` is @inaccessible but is used in the default value of `Referencer3.someField`, which is in the API schema. + + - Type `Referencer5` is in the API schema but all of its members are @inaccessible. + + - Enum value `Enum.PRIVATE_VALUE` is @inaccessible but is used in the default value of `@referencer4(someArg:)`, which is in the API schema. + "###); +} + +#[test] +fn removes_inaccessible_enum_values() { + let api_schema = inaccessible_to_api_schema( + r#" + type Query { + someField: String + } + + # Inaccessible (and non-inaccessible) enum value + enum Enum { + SOME_VALUE + PRIVATE_VALUE @inaccessible + } + + # Inaccessible enum value referenced by default value of inaccessible + # object field argument + type Referencer1 implements Referencer4 { + someField( + privateArg: Enum = PRIVATE_VALUE @inaccessible + ): String + } + + # Inaccessible enum value referenced by default value of non-inaccessible + # object field argument with inaccessible parent + type Referencer2 implements Referencer5 { + someField: String + privateField( + privateArg: Enum! = PRIVATE_VALUE + ): String @inaccessible + } + + # Inaccessible enum value referenced by default value of non-inaccessible + # object field argument with inaccessible grandparent + type Referencer3 implements Referencer6 @inaccessible { + privateField(privateArg: Enum! = PRIVATE_VALUE): String + } + + # Inaccessible enum value referenced by default value of inaccessible + # interface field argument + interface Referencer4 { + someField( + privateArg: Enum = PRIVATE_VALUE @inaccessible + ): String + } + + # Inaccessible enum value referenced by default value of non-inaccessible + # interface field argument with inaccessible parent + interface Referencer5 { + someField: String + privateField( + privateArg: Enum! = PRIVATE_VALUE + ): String @inaccessible + } + + # Inaccessible enum value referenced by default value of non-inaccessible + # interface field argument with inaccessible grandparent + interface Referencer6 @inaccessible { + privateField(privateArg: Enum! = PRIVATE_VALUE): String + } + + # Inaccessible enum value referenced by default value of inaccessible + # input object field + input Referencer7 { + someField: String + privateField: Enum = PRIVATE_VALUE @inaccessible + } + + # Inaccessible enum value referenced by default value of non-inaccessible + # input object field with inaccessible parent + input Referencer8 @inaccessible { + privateField: Enum! = PRIVATE_VALUE + } + + # Inaccessible enum value referenced by default value of inaccessible + # directive argument + directive @referencer9( + privateArg: Enum = PRIVATE_VALUE @inaccessible + ) on FRAGMENT_SPREAD + + # Inaccessible enum value not referenced (but type is referenced) by + # default value of object field argument in the API schema + type Referencer10 { + someField(privateArg: Enum = SOME_VALUE): String + } + + # Inaccessible enum value with an inaccessible parent and no + # non-inaccessible siblings + enum Referencer11 @inaccessible { + PRIVATE_VALUE @inaccessible + OTHER_PRIVATE_VALUE @inaccessible + } + "#, + ) + .expect("should succeed"); + + assert!(coord!(Enum.SOME_VALUE) + .lookup_enum_value(&api_schema) + .is_ok()); + assert!(coord!(Enum.PRIVATE_VALUE) + .lookup_enum_value(&api_schema) + .is_err()); + assert!(coord!(Referencer1.someField).lookup(&api_schema).is_ok()); + assert!(coord!(Referencer1.someField(privateArg:)) + .lookup(&api_schema) + .is_err()); + assert!(coord!(Referencer2.someField).lookup(&api_schema).is_ok()); + assert!(coord!(Referencer2.privateField) + .lookup(&api_schema) + .is_err()); + assert!(!api_schema.types.contains_key("Referencer3")); + assert!(coord!(Referencer4.someField).lookup(&api_schema).is_ok()); + assert!(coord!(Referencer4.someField(privateArg:)) + .lookup(&api_schema) + .is_err()); + assert!(coord!(Referencer5.someField).lookup(&api_schema).is_ok()); + assert!(coord!(Referencer5.privateField) + .lookup(&api_schema) + .is_err()); + assert!(!api_schema.types.contains_key("Referencer6")); + assert!(coord!(Referencer7.someField) + .lookup_input_field(&api_schema) + .is_ok()); + assert!(coord!(Referencer7.privatefield) + .lookup_input_field(&api_schema) + .is_err()); + assert!(!api_schema.types.contains_key("Referencer8")); + assert!(coord!(@referencer9).lookup(&api_schema).is_ok()); + assert!(coord!(@referencer9(privateArg:)) + .lookup(&api_schema) + .is_err()); + assert!(coord!(Referencer10.someField(privateArg:)) + .lookup(&api_schema) + .is_ok()); + assert!(!api_schema.types.contains_key("Referencer11")); +} + +#[test] +fn inaccessible_complex_default_values() { + let errors = inaccessible_to_api_schema( + r#" + type Query { + someField(arg1: [[RootInputObject!]]! = [ + { + foo: { + # 2 references (with nesting) + privateField: [PRIVATE_VALUE] + } + bar: SOME_VALUE + # 0 references since scalar + baz: { privateField: PRIVATE_VALUE } + }, + [{ + foo: [{ + someField: "foo" + }] + # 1 reference + bar: PRIVATE_VALUE + }] + ]): String + } + + input RootInputObject { + foo: [NestedInputObject] + bar: Enum! + baz: Scalar! = { default: 4 } + } + + input NestedInputObject { + someField: String + privateField: [Enum!] @inaccessible + } + + enum Enum { + SOME_VALUE + PRIVATE_VALUE @inaccessible + } + + scalar Scalar + "#, + ) + .expect_err("should return validation errors"); + + insta::assert_snapshot!(errors, @r###" + The following errors occurred: + + - Input field `NestedInputObject.privateField` is @inaccessible but is used in the default value of `Query.someField(arg1:)`, which is in the API schema. + + - Enum value `Enum.PRIVATE_VALUE` is @inaccessible but is used in the default value of `Query.someField(arg1:)`, which is in the API schema. + + - Enum value `Enum.PRIVATE_VALUE` is @inaccessible but is used in the default value of `Query.someField(arg1:)`, which is in the API schema. + "###); +} + +/// It's not GraphQL-spec-compliant to allow a string for an enum value, but +/// since we're allowing it, we need to make sure this logic keeps working +/// until we're allowed to make breaking changes and remove it. +#[test] +fn inaccessible_enum_value_as_string() { + let errors = inaccessible_to_api_schema( + r#" + type Query { + someField(arg1: Enum! = "PRIVATE_VALUE"): String + } + + enum Enum { + SOME_VALUE + PRIVATE_VALUE @inaccessible + } + "#, + ) + .expect_err("should return validation errors"); + + insta::assert_snapshot!(errors, @r###" + The following errors occurred: + + - Enum value `Enum.PRIVATE_VALUE` is @inaccessible but is used in the default value of `Query.someField(arg1:)`, which is in the API schema. + "###); +} + +#[test] +fn inaccessible_directive_arguments_with_accessible_references() { + let errors = inaccessible_to_api_schema( + r#" + type Query { + someField: String + } + + # Inaccessible directive argument + directive @directive(privateArg: String @inaccessible) on SUBSCRIPTION + + # Inaccessible directive argument can't be a required field + directive @directiveRequired( + someArg: String + privateArg: String! @inaccessible + ) on FRAGMENT_DEFINITION + "#, + ) + .expect_err("should return validation errors"); + + insta::assert_snapshot!(errors, @r###" + The following errors occurred: + + - Argument `@directiveRequired(privateArg:)` is @inaccessible but is a required argument of its directive. + "###); +} + +#[test] +fn removes_inaccessible_directive_arguments() { + let api_schema = inaccessible_to_api_schema( + r#" + type Query { + someField: String + } + + # Inaccessible (and non-inaccessible) directive argument + directive @directive( + someArg: String + privateArg: String @inaccessible + ) on QUERY + + # Inaccessible non-nullable directive argument with default + directive @directiveDefault( + someArg: String + privateArg: String! = "default" @inaccessible + ) on MUTATION + "#, + ) + .expect("should succeed"); + + assert!(coord!(@directive(someArg:)).lookup(&api_schema).is_ok()); + assert!(coord!(@directive(privateArg:)).lookup(&api_schema).is_err()); + assert!(coord!(@directiveDefault(someArg:)) + .lookup(&api_schema) + .is_ok()); + assert!(coord!(@directiveDefault(privateArg:)) + .lookup(&api_schema) + .is_err()); +} + +#[test] +fn inaccessible_directive_on_schema_elements() { + let errors = inaccessible_to_api_schema( + r#" + type Query { + someField: String + } + + directive @foo(arg1: String @inaccessible) repeatable on OBJECT + + directive @bar(arg2: String, arg3: String @inaccessible) repeatable on SCHEMA | FIELD + "#, + ) + .expect_err("should return validation errors"); + + insta::assert_snapshot!(errors, @r###" + The following errors occurred: + + - Directive `@foo` cannot use @inaccessible because it may be applied to these type-system locations: OBJECT + + - Directive `@bar` cannot use @inaccessible because it may be applied to these type-system locations: SCHEMA + "###); +} + +#[test] +fn inaccessible_on_builtins() { + let errors = inaccessible_to_api_schema( + r#" + type Query { + someField: String + } + + # Built-in scalar + scalar String @inaccessible + + # Built-in directive + directive @deprecated( + reason: String = "No longer supported" @inaccessible + ) on FIELD_DEFINITION | ENUM_VALUE + "#, + ) + .expect_err("should return validation errors"); + + // Note this is different from the JS implementation + insta::assert_snapshot!(errors, @r###" + The following errors occurred: + + - built-in scalar definitions must be omitted + "###); +} + +#[test] +fn inaccessible_on_imported_elements() { + // TODO(@goto-bus-stop): this `@link`s the join spec but doesn't use it, just because the + // testing code goes through the Supergraph API. See comment at top of file + let graph = Supergraph::new( + r#" + directive @link(url: String!, as: String, import: [link__Import] @inaccessible, for: link__Purpose) repeatable on SCHEMA + + scalar link__Import + + enum link__Purpose { + EXECUTION @inaccessible + SECURITY + } + + directive @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION + + schema + @link(url: "https://specs.apollo.dev/link/v1.0") + @link(url: "https://specs.apollo.dev/join/v0.2") + @link(url: "https://specs.apollo.dev/inaccessible/v0.2") + @link(url: "http://localhost/foo/v1.0") + { + query: Query + } + + type Query { + someField: String! + } + + # Object type + type foo__Object1 @inaccessible { + foo__Field: String! + } + + # Object field + type foo__Object2 implements foo__Interface2 { + foo__Field: foo__Enum1! @inaccessible + } + + # Object field argument + type foo__Object3 { + someField(someArg: foo__Enum1 @inaccessible): foo__Enum2! + } + + # Interface type + interface foo__Interface1 @inaccessible { + foo__Field: String! + } + + # Interface field + interface foo__Interface2 { + foo__Field: foo__Enum1! @inaccessible + } + + # Interface field argument + interface foo__Interface3 { + someField(someArg: foo__InputObject1 @inaccessible): foo__Enum2! + } + + # Union type + union foo__Union @inaccessible = foo__Object1 | foo__Object2 | foo__Object3 + + # Input object type + input foo__InputObject1 @inaccessible { + someField: foo__Enum1 + } + + # Input object field + input foo__InputObject2 { + someField: foo__Scalar @inaccessible + } + + # Enum type + enum foo__Enum1 @inaccessible { + someValue + } + + # Enum value + enum foo__Enum2 { + someValue @inaccessible + } + + # Scalar type + scalar foo__Scalar @inaccessible + + # Directive argument + directive @foo(arg: foo__InputObject2 @inaccessible) repeatable on OBJECT + "#, + ) + .unwrap(); + + let errors = graph + .to_api_schema(Default::default()) + .expect_err("should return validation errors"); + + insta::assert_snapshot!(errors, @r###" + The following errors occurred: + + - Core feature type `link__Purpose` cannot use @inaccessible. + + - Core feature type `foo__Object1` cannot use @inaccessible. + + - Core feature type `foo__Object2` cannot use @inaccessible. + + - Core feature type `foo__Object3` cannot use @inaccessible. + + - Core feature type `foo__Interface1` cannot use @inaccessible. + + - Core feature type `foo__Interface2` cannot use @inaccessible. + + - Core feature type `foo__Interface3` cannot use @inaccessible. + + - Core feature type `foo__Union` cannot use @inaccessible. + + - Core feature type `foo__InputObject1` cannot use @inaccessible. + + - Core feature type `foo__InputObject2` cannot use @inaccessible. + + - Core feature type `foo__Enum1` cannot use @inaccessible. + + - Core feature type `foo__Enum2` cannot use @inaccessible. + + - Core feature type `foo__Scalar` cannot use @inaccessible. + + - Core feature directive `@link` cannot use @inaccessible. + + - Core feature directive `@foo` cannot use @inaccessible. + "###); +} + +#[test] +fn propagates_default_input_values() { + let api_schema = inaccessible_to_api_schema( + r#" + type Query { + field(input: Input = { one: 0, nested: { one: 2 } }): Int + } + input Input { + one: Int! = 1 + two: Int! = 2 + three: Int! = 3 + object: InputObject = { value: 2 } + nested: Nested + nestedWithDefault: Nested! = {} + } + input InputObject { + value: Int + } + input Nested { + noDefault: String + one: Int! = 1 + two: Int! = 2 + default: String = "default" + } + "#, + ) + .expect("should succeed"); + + insta::assert_snapshot!(api_schema, @r###" + type Query { + field(input: Input = {one: 0, nested: {one: 2, two: 2, default: "default"}, two: 2, three: 3, object: {value: 2}, nestedWithDefault: {one: 1, two: 2, default: "default"}}): Int + } + + input Input { + one: Int! = 1 + two: Int! = 2 + three: Int! = 3 + object: InputObject = { + value: 2, + } + nested: Nested + nestedWithDefault: Nested! = { + one: 1, + two: 2, + default: "default", + } + } + + input InputObject { + value: Int + } + + input Nested { + noDefault: String + one: Int! = 1 + two: Int! = 2 + default: String = "default" + } + "###); +} + +#[test] +fn matches_graphql_js_directive_applications() { + let api_schema = inaccessible_to_api_schema( + r#" + type Query { + a: Int @deprecated + b: Int @deprecated(reason: null) + c: Int @deprecated(reason: "Reason") + d: Int @deprecated(reason: "No longer supported") + } + "#, + ) + .expect("should succeed"); + + insta::assert_snapshot!(api_schema, @r###" + type Query { + a: Int @deprecated + b: Int + c: Int @deprecated(reason: "Reason") + d: Int @deprecated + } + "###); +} + +#[test] +fn matches_graphql_js_default_value_propagation() { + let api_schema = inaccessible_to_api_schema( + r#" + type Query { + defaultShouldBeRemoved(arg: OneRequiredOneDefault = {}): Int + defaultShouldHavePropagatedValues(arg: OneOptionalOneDefault = {}): Int + } + + input OneOptionalOneDefault { + notDefaulted: Int + defaulted: Boolean = false + } + + input OneRequiredOneDefault { + notDefaulted: Int! + defaulted: Boolean = false + } + "#, + ) + .expect("should succeed"); + + insta::assert_snapshot!(api_schema, @r###" + type Query { + defaultShouldBeRemoved(arg: OneRequiredOneDefault): Int + defaultShouldHavePropagatedValues(arg: OneOptionalOneDefault = {defaulted: false}): Int + } + + input OneOptionalOneDefault { + notDefaulted: Int + defaulted: Boolean = false + } + + input OneRequiredOneDefault { + notDefaulted: Int! + defaulted: Boolean = false + } + "###); +} + +#[test] +fn remove_referencing_directive_argument() { + let api_schema = inaccessible_to_api_schema( + r#" + extend schema @link(url: "https://example.com/directives/v0.0", as: "d") + + # Set up a chain of core feature directives + # that are referenced in each other's arguments + # to make sure we remove directives safely + directive @d__example_2( + arg1: Int @d__example(arg: 1) + arg: Int @d__arg + ) on ARGUMENT_DEFINITION | FIELD + directive @d__arg( + arg: Int + ) on ARGUMENT_DEFINITION | FIELD + directive @d__example( + arg: Int! @d__arg + ) on ARGUMENT_DEFINITION | FIELD + directive @d__example_3( + arg: Int! @d__example_2 + ) on ARGUMENT_DEFINITION | FIELD + + type Query { + a: Int + } + "#, + ) + .expect("should succeed"); + + insta::assert_snapshot!(api_schema, @r###" + type Query { + a: Int + } + "###); +} + +#[test] +fn include_supergraph_directives() -> Result<(), FederationError> { + let sdl = format!( + " + {INACCESSIBLE_V02_HEADER} + type Query {{ + a: Int + }} + " + ); + let graph = Supergraph::new(&sdl)?; + let api_schema = graph.to_api_schema(ApiSchemaOptions { + include_defer: true, + include_stream: true, + })?; + + insta::assert_snapshot!(api_schema.schema(), @r###" + directive @defer(label: String, if: Boolean! = true) on FRAGMENT_SPREAD | INLINE_FRAGMENT + + directive @stream(label: String, if: Boolean! = true, initialCount: Int = 0) on FIELD + + type Query { + a: Int + } + "###); + + Ok(()) +} + +#[test] +fn supports_core_directive_supergraph() { + let sdl = r#" +schema + @core(feature: "https://specs.apollo.dev/core/v0.2") + @core(feature: "https://specs.apollo.dev/join/v0.2") +{ + query: Query +} + +directive @core(feature: String!, as: String) repeatable on SCHEMA + +directive @join__field( + graph: join__Graph + requires: join__FieldSet + provides: join__FieldSet +) on FIELD_DEFINITION + +directive @join__type( + graph: join__Graph! + key: join__FieldSet +) repeatable on OBJECT | INTERFACE + +directive @join__owner(graph: join__Graph!) on OBJECT | INTERFACE + +directive @join__graph(name: String!, url: String!) on ENUM_VALUE + +scalar join__FieldSet + +enum join__Graph { + ACCOUNTS @join__graph(name: "accounts", url: "http://localhost:4001/graphql") +} + +type Query { + me: String +} + "#; + + let graph = Supergraph::new(sdl).expect("should succeed"); + let api_schema = graph + .to_api_schema(Default::default()) + .expect("should succeed"); + + insta::assert_snapshot!(api_schema.schema(), @r###" + type Query { + me: String + } + "###); +} diff --git a/apollo-federation/tests/composition_tests.rs b/apollo-federation/tests/composition_tests.rs new file mode 100644 index 0000000000..44a234c2e5 --- /dev/null +++ b/apollo-federation/tests/composition_tests.rs @@ -0,0 +1,200 @@ +use apollo_compiler::Schema; +use apollo_federation::subgraph::Subgraph; +use apollo_federation::Supergraph; + +fn print_sdl(schema: &Schema) -> String { + let mut schema = schema.clone(); + schema.types.sort_keys(); + schema.directive_definitions.sort_keys(); + schema.to_string() +} + +#[test] +fn can_compose_supergraph() { + let s1 = Subgraph::parse_and_expand( + "Subgraph1", + "https://subgraph1", + r#" + type Query { + t: T + } + + type T @key(fields: "k") { + k: ID + } + + type S { + x: Int + } + + union U = S | T + "#, + ) + .unwrap(); + let s2 = Subgraph::parse_and_expand( + "Subgraph2", + "https://subgraph2", + r#" + type T @key(fields: "k") { + k: ID + a: Int + b: String + } + + enum E { + V1 + V2 + } + "#, + ) + .unwrap(); + + let supergraph = Supergraph::compose(vec![&s1, &s2]).unwrap(); + insta::assert_snapshot!(print_sdl(supergraph.schema.schema())); + insta::assert_snapshot!(print_sdl( + supergraph + .to_api_schema(Default::default()) + .unwrap() + .schema() + )); +} + +#[test] +fn can_compose_with_descriptions() { + let s1 = Subgraph::parse_and_expand( + "Subgraph1", + "https://subgraph1", + r#" + "The foo directive description" + directive @foo(url: String) on FIELD + + "A cool schema" + schema { + query: Query + } + + """ + Available queries + Not much yet + """ + type Query { + "Returns tea" + t( + "An argument that is very important" + x: String! + ): String + } + "#, + ) + .unwrap(); + + let s2 = Subgraph::parse_and_expand( + "Subgraph2", + "https://subgraph2", + r#" + "The foo directive description" + directive @foo(url: String) on FIELD + + "An enum" + enum E { + "The A value" + A + "The B value" + B + } + "#, + ) + .unwrap(); + + let supergraph = Supergraph::compose(vec![&s1, &s2]).unwrap(); + insta::assert_snapshot!(print_sdl(supergraph.schema.schema())); + insta::assert_snapshot!(print_sdl( + supergraph + .to_api_schema(Default::default()) + .unwrap() + .schema() + )); +} + +#[test] +fn can_compose_types_from_different_subgraphs() { + let s1 = Subgraph::parse_and_expand( + "SubgraphA", + "https://subgraphA", + r#" + type Query { + products: [Product!] + } + + type Product { + sku: String! + name: String! + } + "#, + ) + .unwrap(); + + let s2 = Subgraph::parse_and_expand( + "SubgraphB", + "https://subgraphB", + r#" + type User { + name: String + email: String! + } + "#, + ) + .unwrap(); + let supergraph = Supergraph::compose(vec![&s1, &s2]).unwrap(); + insta::assert_snapshot!(print_sdl(supergraph.schema.schema())); + insta::assert_snapshot!(print_sdl( + supergraph + .to_api_schema(Default::default()) + .unwrap() + .schema() + )); +} + +#[test] +fn compose_removes_federation_directives() { + let s1 = Subgraph::parse_and_expand( + "SubgraphA", + "https://subgraphA", + r#" + extend schema @link(url: "https://specs.apollo.dev/federation/v2.5", import: [ "@key", "@provides", "@external" ]) + + type Query { + products: [Product!] @provides(fields: "name") + } + + type Product @key(fields: "sku") { + sku: String! + name: String! @external + } + "#, + ) + .unwrap(); + + let s2 = Subgraph::parse_and_expand( + "SubgraphB", + "https://subgraphB", + r#" + extend schema @link(url: "https://specs.apollo.dev/federation/v2.5", import: [ "@key", "@shareable" ]) + + type Product @key(fields: "sku") { + sku: String! + name: String! @shareable + } + "#, + ) + .unwrap(); + + let supergraph = Supergraph::compose(vec![&s1, &s2]).unwrap(); + insta::assert_snapshot!(print_sdl(supergraph.schema.schema())); + insta::assert_snapshot!(print_sdl( + supergraph + .to_api_schema(Default::default()) + .unwrap() + .schema() + )); +} diff --git a/apollo-federation/tests/extract_subgraphs.rs b/apollo-federation/tests/extract_subgraphs.rs new file mode 100644 index 0000000000..51a505345f --- /dev/null +++ b/apollo-federation/tests/extract_subgraphs.rs @@ -0,0 +1,257 @@ +use apollo_compiler::coord; +use apollo_compiler::schema::Value; +use apollo_compiler::Node; +use apollo_federation::Supergraph; + +#[test] +fn can_extract_subgraph() { + let schema = r#" + schema + @link(url: "https://specs.apollo.dev/link/v1.0") + @link(url: "https://specs.apollo.dev/join/v0.3", for: EXECUTION) + { + query: Query + } + + directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE + + directive @join__field(graph: join__Graph, requires: join__FieldSet, provides: join__FieldSet, type: String, external: Boolean, override: String, usedOverridden: Boolean) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION + + directive @join__graph(name: String!, url: String!) on ENUM_VALUE + + directive @join__implements(graph: join__Graph!, interface: String!) repeatable on OBJECT | INTERFACE + + directive @join__type(graph: join__Graph!, key: join__FieldSet, extension: Boolean! = false, resolvable: Boolean! = true, isInterfaceObject: Boolean! = false) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR + + directive @join__unionMember(graph: join__Graph!, member: String!) repeatable on UNION + + directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA + + enum E + @join__type(graph: SUBGRAPH2) + { + V1 @join__enumValue(graph: SUBGRAPH2) + V2 @join__enumValue(graph: SUBGRAPH2) + } + + scalar join__FieldSet + + enum join__Graph { + SUBGRAPH1 @join__graph(name: "Subgraph1", url: "https://Subgraph1") + SUBGRAPH2 @join__graph(name: "Subgraph2", url: "https://Subgraph2") + } + + scalar link__Import + + enum link__Purpose { + """ + \`SECURITY\` features provide metadata necessary to securely resolve fields. + """ + SECURITY + + """ + \`EXECUTION\` features provide metadata necessary for operation execution. + """ + EXECUTION + } + + type Query + @join__type(graph: SUBGRAPH1) + @join__type(graph: SUBGRAPH2) + { + t: T @join__field(graph: SUBGRAPH1) + } + + type S + @join__type(graph: SUBGRAPH1) + { + x: Int + } + + type T + @join__type(graph: SUBGRAPH1, key: "k") + @join__type(graph: SUBGRAPH2, key: "k") + { + k: ID + a: Int @join__field(graph: SUBGRAPH2) + b: String @join__field(graph: SUBGRAPH2) + } + + union U + @join__type(graph: SUBGRAPH1) + @join__unionMember(graph: SUBGRAPH1, member: "S") + @join__unionMember(graph: SUBGRAPH1, member: "T") + = S | T + "#; + + let supergraph = Supergraph::new(schema).unwrap(); + let subgraphs = supergraph + .extract_subgraphs() + .expect("Should have been able to extract subgraphs"); + + let mut snapshot = String::new(); + for (_name, subgraph) in subgraphs { + use std::fmt::Write; + + _ = writeln!( + &mut snapshot, + "{}: {}\n---\n{}", + subgraph.name, + subgraph.url, + subgraph.schema.schema() + ); + } + insta::assert_snapshot!(snapshot); +} + +#[test] +fn preserve_default_values_of_input_fields() { + let supergraph = Supergraph::new(r#" + schema + @link(url: "https://specs.apollo.dev/link/v1.0") + @link(url: "https://specs.apollo.dev/join/v0.2", for: EXECUTION) + { + query: Query + } + + directive @join__field(graph: join__Graph!, requires: join__FieldSet, provides: join__FieldSet, type: String, external: Boolean, override: String, usedOverridden: Boolean) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION + + directive @join__graph(name: String!, url: String!) on ENUM_VALUE + + directive @join__implements(graph: join__Graph!, interface: String!) repeatable on OBJECT | INTERFACE + + directive @join__type(graph: join__Graph!, key: join__FieldSet, extension: Boolean! = false, resolvable: Boolean! = true) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR + + directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA + + input Input + @join__type(graph: SERVICE) + { + a: Int! = 1234 + } + + scalar join__FieldSet + + enum join__Graph { + SERVICE @join__graph(name: "service", url: "") + } + + scalar link__Import + + enum link__Purpose { + """ + \`SECURITY\` features provide metadata necessary to securely resolve fields. + """ + SECURITY + + """ + \`EXECUTION\` features provide metadata necessary for operation execution. + """ + EXECUTION + } + + type Query + @join__type(graph: SERVICE) + { + field(input: Input!): String + } + "#).expect("should parse"); + + let subgraphs = supergraph + .extract_subgraphs() + .expect("should extract subgraphs"); + + let service = subgraphs + .get("service") + .expect("missing subgraph") + .schema + .schema(); + let field_a = coord!(Input.a).lookup_input_field(service).unwrap(); + assert_eq!( + field_a.default_value, + Some(Node::new(Value::Int(1234.into()))) + ); +} + +#[test] +fn erase_empty_types_due_to_overridden_fields() { + let supergraph = Supergraph::new(r#" + schema + @link(url: "https://specs.apollo.dev/link/v1.0") + @link(url: "https://specs.apollo.dev/join/v0.3", for: EXECUTION) + @link(url: "https://specs.apollo.dev/tag/v0.3") + { + query: Query + } + + directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE + + directive @join__field(graph: join__Graph, requires: join__FieldSet, provides: join__FieldSet, type: String, external: Boolean, override: String, usedOverridden: Boolean) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION + + directive @join__graph(name: String!, url: String!) on ENUM_VALUE + + directive @join__implements(graph: join__Graph!, interface: String!) repeatable on OBJECT | INTERFACE + + directive @join__type(graph: join__Graph!, key: join__FieldSet, extension: Boolean! = false, resolvable: Boolean! = true, isInterfaceObject: Boolean! = false) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR + + directive @join__unionMember(graph: join__Graph!, member: String!) repeatable on UNION + + directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA + + directive @tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA + input Input + @join__type(graph: B) + { + a: Int! = 1234 + } + + scalar join__FieldSet + + enum join__Graph { + A @join__graph(name: "a", url: "") + B @join__graph(name: "b", url: "") + } + + scalar link__Import + + enum link__Purpose { + """ + \`SECURITY\` features provide metadata necessary to securely resolve fields. + """ + SECURITY + + """ + \`EXECUTION\` features provide metadata necessary for operation execution. + """ + EXECUTION + } + + type Query + @join__type(graph: A) + { + field: String + } + + type User + @join__type(graph: A) + @join__type(graph: B) + { + foo: String @join__field(graph: A, override: "b") + + bar: String @join__field(graph: A) + + baz: String @join__field(graph: A) + } + "#).expect("should parse"); + + let subgraphs = supergraph + .extract_subgraphs() + .expect("should extract subgraphs"); + + let b = subgraphs + .get("b") + .expect("missing subgraph") + .schema + .schema(); + assert!(!b.types.contains_key("User")); +} diff --git a/apollo-federation/tests/main.rs b/apollo-federation/tests/main.rs new file mode 100644 index 0000000000..9d6a919307 --- /dev/null +++ b/apollo-federation/tests/main.rs @@ -0,0 +1,5 @@ +mod api_schema; +mod composition_tests; +mod extract_subgraphs; +mod query_plan; +mod subgraph; diff --git a/apollo-federation/tests/query_plan/build_query_plan_support.rs b/apollo-federation/tests/query_plan/build_query_plan_support.rs new file mode 100644 index 0000000000..87439fa18a --- /dev/null +++ b/apollo-federation/tests/query_plan/build_query_plan_support.rs @@ -0,0 +1,186 @@ +use std::collections::HashSet; +use std::io::Read; +use std::sync::Mutex; +use std::sync::OnceLock; + +use apollo_federation::query_plan::query_planner::QueryPlanner; +use apollo_federation::query_plan::query_planner::QueryPlannerConfig; +use apollo_federation::schema::ValidFederationSchema; +use sha1::Digest; + +const ROVER_FEDERATION_VERSION: &str = "2.7.4"; + +// TODO: use 2.7 when join v0.4 is fully supported in this crate +const IMPLICIT_LINK_DIRECTIVE: &str = r#"@link(url: "https://specs.apollo.dev/federation/v2.6", import: ["@key", "@requires", "@provides", "@external", "@tag", "@extends", "@shareable", "@inaccessible", "@override", "@composeDirective", "@interfaceObject"])"#; + +/// Runs composition on the given subgraph schemas and return `(api_schema, query_planner)` +/// +/// Results of composition are cached in `tests/query_plan/supergraphs`. +/// When needed, composition is done by starting a Rover subprocess +/// (this requires a recent-enough version of `rover` to be in `$PATH`) +/// but only if the `USE_ROVER=1` env variable is set. +/// +/// Panics if composition is needed but `USE_ROVER` is unset. +/// +/// This can all be remove when composition is implemented in Rust. +macro_rules! planner { + ( + $( config = $config: expr, )? + $( $subgraph_name: ident: $subgraph_schema: expr),+ + $(,)? + ) => {{ + #[allow(unused_mut)] + let mut config = Default::default(); + $( config = $config )? + $crate::query_plan::build_query_plan_support::api_schema_and_planner( + insta::_function_name!(), + config, + &[ $( (stringify!($subgraph_name), $subgraph_schema) ),+ ], + ) + }}; +} + +/// Takes a reference to the result of `planner!()`, an operation string, and an expected +/// formatted query plan string. +/// Run `cargo insta review` to diff and accept changes to the generated query plan. +macro_rules! assert_plan { + ($api_schema_and_planner: expr, $operation: expr, @$expected: literal) => {{ + let (api_schema, planner) = $api_schema_and_planner; + let document = apollo_compiler::ExecutableDocument::parse_and_validate( + api_schema.schema(), + $operation, + "operation.graphql", + ) + .unwrap(); + insta::assert_snapshot!(planner.build_query_plan(&document, None).unwrap(), @$expected); + }}; +} + +#[track_caller] +pub(crate) fn api_schema_and_planner( + function_path: &'static str, + config: QueryPlannerConfig, + subgraph_names_and_schemas: &[(&str, &str)], +) -> (ValidFederationSchema, QueryPlanner) { + let supergraph = compose(function_path, subgraph_names_and_schemas); + let supergraph = apollo_federation::Supergraph::new(&supergraph).unwrap(); + let planner = QueryPlanner::new(&supergraph, config).unwrap(); + let api_schema_config = apollo_federation::ApiSchemaOptions { + include_defer: true, + include_stream: false, + }; + let api_schema = supergraph.to_api_schema(api_schema_config).unwrap(); + (api_schema, planner) +} + +#[track_caller] +pub(crate) fn compose( + function_path: &'static str, + subgraph_names_and_schemas: &[(&str, &str)], +) -> String { + let unique_names: std::collections::HashSet<_> = subgraph_names_and_schemas + .iter() + .map(|(name, _)| name) + .collect(); + assert!( + unique_names.len() == subgraph_names_and_schemas.len(), + "subgraph names must be unique" + ); + + let subgraph_names_and_schemas: Vec<_> = subgraph_names_and_schemas + .iter() + .map(|(name, schema)| { + ( + *name, + format!("extend schema {IMPLICIT_LINK_DIRECTIVE}\n\n{}", schema,), + ) + }) + .collect(); + + let mut hasher = sha1::Sha1::new(); + hasher.update(ROVER_FEDERATION_VERSION); + for (name, schema) in &subgraph_names_and_schemas { + hasher.update(b"\xFF"); + hasher.update(name); + hasher.update(b"\xFF"); + hasher.update(schema); + } + let expected_hash = hex::encode(hasher.finalize()); + let prefix = "# Composed from subgraphs with hash: "; + + let test_name = function_path.rsplit("::").next().unwrap(); + static SEEN_TEST_NAMES: OnceLock>> = OnceLock::new(); + let new = SEEN_TEST_NAMES + .get_or_init(Default::default) + .lock() + .unwrap() + .insert(test_name); + assert!( + new, + "planner!() can only be used once in test(s) named '{test_name}'" + ); + let supergraph_path = std::path::PathBuf::from(std::env::var_os("CARGO_MANIFEST_DIR").unwrap()) + .join("tests") + .join("query_plan") + .join("supergraphs") + .join(format!("{test_name}.graphql",)); + let supergraph = match std::fs::read_to_string(&supergraph_path) { + Ok(contents) => { + if let Some(hash) = contents + .lines() + .next() + .unwrap_or_default() + .strip_prefix(prefix) + { + if hash == expected_hash { + Ok(contents) + } else { + Err("outdated") + } + } else { + Err("malformed") + } + } + Err(e) if e.kind() == std::io::ErrorKind::NotFound => Err("missing"), + Err(e) => panic!("{e}"), + }; + supergraph.unwrap_or_else(|reason| { + if std::env::var_os("USE_ROVER").is_none() { + panic!( + "Composed supergraph schema file {} is {reason}. \ + Make sure `rover` is in $PATH and re-run with `USE_ROVER=1` \ + env variable to update it.", + supergraph_path.display() + ) + } + let temp_dir = tempfile::tempdir().unwrap(); + let temp_dir = temp_dir.path(); + let mut config = format!("federation_version: ={ROVER_FEDERATION_VERSION}\nsubgraphs:\n"); + for (name, schema) in subgraph_names_and_schemas { + let subgraph_path = temp_dir.join(format!("{name}.graphql")); + config.push_str(&format!( + " {name}:\n routing_url: none\n schema:\n file: {}\n", + subgraph_path.display() + )); + std::fs::write(subgraph_path, schema).unwrap(); + } + let config_path = temp_dir.join("rover.yaml"); + std::fs::write(&config_path, config).unwrap(); + let mut rover = std::process::Command::new("rover") + .args(["supergraph", "compose", "--config"]) + .arg(config_path) + .stdout(std::process::Stdio::piped()) + .spawn() + .unwrap(); + let mut supergraph = format!("{prefix}{expected_hash}\n"); + rover + .stdout + .take() + .unwrap() + .read_to_string(&mut supergraph) + .unwrap(); + assert!(rover.wait().unwrap().success()); + std::fs::write(supergraph_path, &supergraph).unwrap(); + supergraph + }) +} diff --git a/apollo-federation/tests/query_plan/build_query_plan_tests.rs b/apollo-federation/tests/query_plan/build_query_plan_tests.rs new file mode 100644 index 0000000000..121d99fb73 --- /dev/null +++ b/apollo-federation/tests/query_plan/build_query_plan_tests.rs @@ -0,0 +1,36 @@ +/* +Template to copy-paste: + +#[test] +fn some_name() { + let planner = planner!( + Subgraph1: r#" + type Query { + ... + } + "#, + Subgraph2: r#" + type Query { + ... + } + "#, + ); + assert_plan!( + &planner, + r#" + { + ... + } + "#, + @r###" + QueryPlan { + ... + } + "### + ); +} +*/ + +mod shareable_root_fields; + +// TODO: port the rest of query-planner-js/src/__tests__/buildPlan.test.ts diff --git a/apollo-federation/tests/query_plan/build_query_plan_tests/shareable_root_fields.rs b/apollo-federation/tests/query_plan/build_query_plan_tests/shareable_root_fields.rs new file mode 100644 index 0000000000..5a56842358 --- /dev/null +++ b/apollo-federation/tests/query_plan/build_query_plan_tests/shareable_root_fields.rs @@ -0,0 +1,134 @@ +#[test] +fn can_use_same_root_operation_from_multiple_subgraphs_in_parallel() { + let planner = planner!( + Subgraph1: r#" + type Query { + me: User! @shareable + } + + type User @key(fields: "id") { + id: ID! + prop1: String + } + "#, + Subgraph2: r#" + type Query { + me: User! @shareable + } + + type User @key(fields: "id") { + id: ID! + prop2: String + } + "#, + ); + assert_plan!( + &planner, + r#" + { + me { + prop1 + prop2 + } + } + "#, + @r###" + QueryPlan { + Parallel { + Fetch(service: "Subgraph1") { + { + me { + prop1 + } + } + }, + Fetch(service: "Subgraph2") { + { + me { + prop2 + } + } + }, + }, + } + "### + ); +} + +#[test] +fn handles_root_operation_shareable_in_many_subgraphs() { + let planner = planner!( + Subgraph1: r#" + type User @key(fields: "id") { + id: ID! + f0: Int + f1: Int + f2: Int + f3: Int + } + "#, + Subgraph2: r#" + type Query { + me: User! @shareable + } + + type User @key(fields: "id") { + id: ID! + } + "#, + Subgraph3: r#" + type Query { + me: User! @shareable + } + + type User @key(fields: "id") { + id: ID! + } + "#, + ); + assert_plan!( + &planner, + r#" + { + me { + f0 + f1 + f2 + f3 + } + } + "#, + @r###" + QueryPlan { + Sequence { + Fetch(service: "Subgraph2") { + { + me { + __typename + id + } + } + }, + Flatten(path: "me") { + Fetch(service: "Subgraph1") { + { + ... on User { + __typename + id + } + } => + { + ... on User { + f0 + f1 + f2 + f3 + } + } + }, + }, + }, + } + "### + ); +} diff --git a/apollo-federation/tests/query_plan/mod.rs b/apollo-federation/tests/query_plan/mod.rs new file mode 100644 index 0000000000..ed0b5d44f4 --- /dev/null +++ b/apollo-federation/tests/query_plan/mod.rs @@ -0,0 +1,6 @@ +#[macro_use] +mod build_query_plan_support; +mod build_query_plan_tests; +// TODO: port query-planner-js/src/__tests__/buildPlan.*.test.ts as new modules here +mod operation_optimization_tests; +mod operation_validations_tests; diff --git a/apollo-federation/tests/query_plan/operation_optimization_tests.rs b/apollo-federation/tests/query_plan/operation_optimization_tests.rs new file mode 100644 index 0000000000..3bae447e23 --- /dev/null +++ b/apollo-federation/tests/query_plan/operation_optimization_tests.rs @@ -0,0 +1,1561 @@ +#[test] +fn optimize_fragments_using_other_fragments_when_possible() { + // test('optimize fragments using other fragments when possible', () => { + // const schema = parseSchema(` + // type Query { + // t: I + // } + // + // interface I { + // b: Int + // u: U + // } + // + // type T1 implements I { + // a: Int + // b: Int + // u: U + // } + // + // type T2 implements I { + // x: String + // y: String + // b: Int + // u: U + // } + // + // union U = T1 | T2 + // `); + // + // const operation = parseOperation(schema, ` + // fragment OnT1 on T1 { + // a + // b + // } + // + // fragment OnT2 on T2 { + // x + // y + // } + // + // fragment OnI on I { + // b + // } + // + // fragment OnU on U { + // ...OnI + // ...OnT1 + // ...OnT2 + // } + // + // query { + // t { + // ...OnT1 + // ...OnT2 + // ...OnI + // u { + // ...OnU + // } + // } + // } + // `); + // + // const withoutFragments = operation.expandAllFragments(); + // expect(withoutFragments.toString()).toMatchString(` + // { + // t { + // ... on T1 { + // a + // b + // } + // ... on T2 { + // x + // y + // } + // b + // u { + // ... on I { + // b + // } + // ... on T1 { + // a + // b + // } + // ... on T2 { + // x + // y + // } + // } + // } + // } + // `); + // + // const optimized = withoutFragments.optimize(operation.fragments!); + // expect(optimized.toString()).toMatchString(` + // fragment OnU on U { + // ... on I { + // b + // } + // ... on T1 { + // a + // b + // } + // ... on T2 { + // x + // y + // } + // } + // + // { + // t { + // ...OnU + // u { + // ...OnU + // } + // } + // } + // `); + // }); +} + +#[test] +fn handles_fragments_using_other_fragments() { + //test('handles fragments using other fragments', () => { + // const schema = parseSchema(` + // type Query { + // t: I + // } + // + // interface I { + // b: Int + // c: Int + // u1: U + // u2: U + // } + // + // type T1 implements I { + // a: Int + // b: Int + // c: Int + // me: T1 + // u1: U + // u2: U + // } + // + // type T2 implements I { + // x: String + // y: String + // b: Int + // c: Int + // u1: U + // u2: U + // } + // + // union U = T1 | T2 + // `); + // + // const operation = parseOperation(schema, ` + // fragment OnT1 on T1 { + // a + // b + // } + // + // fragment OnT2 on T2 { + // x + // y + // } + // + // fragment OnI on I { + // b + // c + // } + // + // fragment OnU on U { + // ...OnI + // ...OnT1 + // ...OnT2 + // } + // + // query { + // t { + // ...OnT1 + // ...OnT2 + // u1 { + // ...OnU + // } + // u2 { + // ...OnU + // } + // ... on T1 { + // me { + // ...OnI + // } + // } + // } + // } + // `); + // + // const withoutFragments = operation.expandAllFragments(); + // expect(withoutFragments.toString()).toMatchString(` + // { + // t { + // ... on T1 { + // a + // b + // me { + // b + // c + // } + // } + // ... on T2 { + // x + // y + // } + // u1 { + // ... on I { + // b + // c + // } + // ... on T1 { + // a + // b + // } + // ... on T2 { + // x + // y + // } + // } + // u2 { + // ... on I { + // b + // c + // } + // ... on T1 { + // a + // b + // } + // ... on T2 { + // x + // y + // } + // } + // } + // } + // `); + // + // const optimized = withoutFragments.optimize(operation.fragments!); + // // We should reuse and keep all fragments, because 1) onU is used twice and 2) + // // all the other ones are used once in the query, and once in onU definition. + // expect(optimized.toString()).toMatchString(` + // fragment OnT1 on T1 { + // a + // b + // } + // + // fragment OnT2 on T2 { + // x + // y + // } + // + // fragment OnI on I { + // b + // c + // } + // + // fragment OnU on U { + // ...OnI + // ...OnT1 + // ...OnT2 + // } + // + // { + // t { + // ... on T1 { + // ...OnT1 + // me { + // ...OnI + // } + // } + // ...OnT2 + // u1 { + // ...OnU + // } + // u2 { + // ...OnU + // } + // } + // } + // `); + // }); +} + +#[test] +fn handles_fragments_with_nested_selections() { + //test('handles fragments with nested selections', () => { + // const schema = parseSchema(` + // type Query { + // t1a: T1 + // t2a: T1 + // } + // + // type T1 { + // t2: T2 + // } + // + // type T2 { + // x: String + // y: String + // } + // `); + // + // testFragmentsRoundtrip({ + // schema, + // query: ` + // fragment OnT1 on T1 { + // t2 { + // x + // } + // } + // + // query { + // t1a { + // ...OnT1 + // t2 { + // y + // } + // } + // t2a { + // ...OnT1 + // } + // } + // `, + // expanded: ` + // { + // t1a { + // t2 { + // x + // y + // } + // } + // t2a { + // t2 { + // x + // } + // } + // } + // `, + // }); + // }); +} + +#[test] +fn handles_nested_fragments_with_field_intersection() { + //test('handles nested fragments with field intersection', () => { + // const schema = parseSchema(` + // type Query { + // t: T + // } + // + // type T { + // a: A + // b: Int + // } + // + // type A { + // x: String + // y: String + // z: String + // } + // `); + // + // + // // The subtlety here is that `FA` contains `__typename` and so after we're reused it, the + // // selection will look like: + // // { + // // t { + // // a { + // // ...FA + // // } + // // } + // // } + // // But to recognize that `FT` can be reused from there, we need to be able to see that + // // the `__typename` that `FT` wants is inside `FA` (and since FA applies on the parent type `A` + // // directly, it is fine to reuse). + // testFragmentsRoundtrip({ + // schema, + // query: ` + // fragment FA on A { + // __typename + // x + // y + // } + // + // fragment FT on T { + // a { + // __typename + // ...FA + // } + // } + // + // query { + // t { + // ...FT + // } + // } + // `, + // expanded: ` + // { + // t { + // a { + // __typename + // x + // y + // } + // } + // } + // `, + // }); + // }); +} + +#[test] +fn handles_fragment_matching_subset_of_field_selection() { + // test('handles fragment matching subset of field selection', () => { + // const schema = parseSchema(` + // type Query { + // t: T + // } + // + // type T { + // a: String + // b: B + // c: Int + // d: D + // } + // + // type B { + // x: String + // y: String + // } + // + // type D { + // m: String + // n: String + // } + // `); + // + // testFragmentsRoundtrip({ + // schema, + // query: ` + // fragment FragT on T { + // b { + // __typename + // x + // } + // c + // d { + // m + // } + // } + // + // { + // t { + // ...FragT + // d { + // n + // } + // a + // } + // } + // `, + // expanded: ` + // { + // t { + // b { + // __typename + // x + // } + // c + // d { + // m + // n + // } + // a + // } + // } + // `, + // }); + // }); +} + +#[test] +fn handles_fragment_matching_subset_of_inline_fragment_selection() { + // test('handles fragment matching subset of inline fragment selection', () => { + // // Pretty much the same test than the previous one, but matching inside a fragment selection inside + // // of inside a field selection. + // const schema = parseSchema(` + // type Query { + // i: I + // } + // + // interface I { + // a: String + // } + // + // type T { + // a: String + // b: B + // c: Int + // d: D + // } + // + // type B { + // x: String + // y: String + // } + // + // type D { + // m: String + // n: String + // } + // `); + // + // testFragmentsRoundtrip({ + // schema, + // query: ` + // fragment FragT on T { + // b { + // __typename + // x + // } + // c + // d { + // m + // } + // } + // + // { + // i { + // ... on T { + // ...FragT + // d { + // n + // } + // a + // } + // } + // } + // `, + // expanded: ` + // { + // i { + // ... on T { + // b { + // __typename + // x + // } + // c + // d { + // m + // n + // } + // a + // } + // } + // } + // `, + // }); + // }); +} + +#[test] +fn intersecting_fragments() { + // test('intersecting fragments', () => { + // const schema = parseSchema(` + // type Query { + // t: T + // } + // + // type T { + // a: String + // b: B + // c: Int + // d: D + // } + // + // type B { + // x: String + // y: String + // } + // + // type D { + // m: String + // n: String + // } + // `); + // + // testFragmentsRoundtrip({ + // schema, + // // Note: the code that reuse fragments iterates on fragments in the order they are defined in the document, but when it reuse + // // a fragment, it puts it at the beginning of the selection (somewhat random, it just feel often easier to read), so the net + // // effect on this example is that `Frag2`, which will be reused after `Frag1` will appear first in the re-optimized selection. + // // So we put it first in the input too so that input and output actually match (the `testFragmentsRoundtrip` compares strings, + // // so it is sensible to ordering; we could theoretically use `Operation.equals` instead of string equality, which wouldn't + // // really on ordering, but `Operation.equals` is not entirely trivial and comparing strings make problem a bit more obvious). + // query: ` + // fragment Frag1 on T { + // b { + // x + // } + // c + // d { + // m + // } + // } + // + // fragment Frag2 on T { + // a + // b { + // __typename + // x + // } + // d { + // m + // n + // } + // } + // + // { + // t { + // ...Frag1 + // ...Frag2 + // } + // } + // `, + // expanded: ` + // { + // t { + // b { + // x + // __typename + // } + // c + // d { + // m + // n + // } + // a + // } + // } + // `, + // }); + // }); +} + +#[test] +fn fragments_application_makes_type_condition_trivial() { + // test('fragments whose application makes a type condition trivial', () => { + // const schema = parseSchema(` + // type Query { + // t: T + // } + // + // interface I { + // x: String + // } + // + // type T implements I { + // x: String + // a: String + // } + // `); + // + // testFragmentsRoundtrip({ + // schema, + // query: ` + // fragment FragI on I { + // x + // ... on T { + // a + // } + // } + // + // { + // t { + // ...FragI + // } + // } + // `, + // expanded: ` + // { + // t { + // x + // a + // } + // } + // `, + // }); + // }); +} + +#[test] +fn handles_fragment_matching_at_the_top_level_of_another_fragment() { + //test('handles fragment matching at the top level of another fragment', () => { + // const schema = parseSchema(` + // type Query { + // t: T + // } + // + // type T { + // a: String + // u: U + // } + // + // type U { + // x: String + // y: String + // } + // `); + // + // testFragmentsRoundtrip({ + // schema, + // query: ` + // fragment Frag1 on T { + // a + // } + // + // fragment Frag2 on T { + // u { + // x + // y + // } + // ...Frag1 + // } + // + // fragment Frag3 on Query { + // t { + // ...Frag2 + // } + // } + // + // { + // ...Frag3 + // } + // `, + // expanded: ` + // { + // t { + // u { + // x + // y + // } + // a + // } + // } + // `, + // }); + // }); +} + +#[test] +fn handles_fragments_used_in_context_where_they_get_trimmed() { + //test('handles fragments used in a context where they get trimmed', () => { + // const schema = parseSchema(` + // type Query { + // t1: T1 + // } + // + // interface I { + // x: Int + // } + // + // type T1 implements I { + // x: Int + // y: Int + // } + // + // type T2 implements I { + // x: Int + // z: Int + // } + // `); + // + // testFragmentsRoundtrip({ + // schema, + // query: ` + // fragment FragOnI on I { + // ... on T1 { + // y + // } + // ... on T2 { + // z + // } + // } + // + // { + // t1 { + // ...FragOnI + // } + // } + // `, + // expanded: ` + // { + // t1 { + // y + // } + // } + // `, + // }); + // }); +} + +#[test] +fn handles_fragments_used_in_the_context_of_non_intersecting_abstract_types() { + //test('handles fragments used in the context of non-intersecting abstract types', () => { + // const schema = parseSchema(` + // type Query { + // i2: I2 + // } + // + // interface I1 { + // x: Int + // } + // + // interface I2 { + // y: Int + // } + // + // interface I3 { + // z: Int + // } + // + // type T1 implements I1 & I2 { + // x: Int + // y: Int + // } + // + // type T2 implements I1 & I3 { + // x: Int + // z: Int + // } + // `); + // + // testFragmentsRoundtrip({ + // schema, + // query: ` + // fragment FragOnI1 on I1 { + // ... on I2 { + // y + // } + // ... on I3 { + // z + // } + // } + // + // { + // i2 { + // ...FragOnI1 + // } + // } + // `, + // expanded: ` + // { + // i2 { + // ... on I1 { + // ... on I2 { + // y + // } + // ... on I3 { + // z + // } + // } + // } + // } + // `, + // }); + // }); +} + +#[test] +fn handles_fragments_on_union_in_context_with_limited_intersection() { + //test('handles fragments on union in context with limited intersection', () => { + // const schema = parseSchema(` + // type Query { + // t1: T1 + // } + // + // union U = T1 | T2 + // + // type T1 { + // x: Int + // } + // + // type T2 { + // y: Int + // } + // `); + // + // testFragmentsRoundtrip({ + // schema, + // query: ` + // fragment OnU on U { + // ... on T1 { + // x + // } + // ... on T2 { + // y + // } + // } + // + // { + // t1 { + // ...OnU + // } + // } + // `, + // expanded: ` + // { + // t1 { + // x + // } + // } + // `, + // }); + // }); +} + +#[test] +fn off_by_1_error() { + //test('off by 1 error', () => { + // const schema = buildSchema(`#graphql + // type Query { + // t: T + // } + // type T { + // id: String! + // a: A + // v: V + // } + // type A { + // id: String! + // } + // type V { + // t: T! + // } + // `); + // + // const operation = parseOperation(schema, ` + // { + // t { + // ...TFrag + // v { + // t { + // id + // a { + // __typename + // id + // } + // } + // } + // } + // } + // + // fragment TFrag on T { + // __typename + // id + // } + // `); + // + // const withoutFragments = operation.expandAllFragments(); + // expect(withoutFragments.toString()).toMatchString(` + // { + // t { + // __typename + // id + // v { + // t { + // id + // a { + // __typename + // id + // } + // } + // } + // } + // } + // `); + // + // const optimized = withoutFragments.optimize(operation.fragments!); + // expect(optimized.toString()).toMatchString(` + // fragment TFrag on T { + // __typename + // id + // } + // + // { + // t { + // ...TFrag + // v { + // t { + // ...TFrag + // a { + // __typename + // id + // } + // } + // } + // } + // } + // `); + // }); +} + +#[test] +fn removes_all_unused_fragments() { + //test('does not leave unused fragments', () => { + // const schema = parseSchema(` + // type Query { + // t1: T1 + // } + // + // union U1 = T1 | T2 | T3 + // union U2 = T2 | T3 + // + // type T1 { + // x: Int + // } + // + // type T2 { + // y: Int + // } + // + // type T3 { + // z: Int + // } + // `); + // const gqlSchema = schema.toGraphQLJSSchema(); + // + // const operation = parseOperation(schema, ` + // query { + // t1 { + // ...Outer + // } + // } + // + // fragment Outer on U1 { + // ... on T1 { + // x + // } + // ... on T2 { + // ... Inner + // } + // ... on T3 { + // ... Inner + // } + // } + // + // fragment Inner on U2 { + // ... on T2 { + // y + // } + // } + // `); + // expect(validate(gqlSchema, parse(operation.toString()))).toStrictEqual([]); + // + // const withoutFragments = operation.expandAllFragments(); + // expect(withoutFragments.toString()).toMatchString(` + // { + // t1 { + // x + // } + // } + // `); + // + // // This is a bit of contrived example, but the reusing code will be able + // // to figure out that the `Outer` fragment can be reused and will initially + // // do so, but it's only use once, so it will expand it, which yields: + // // { + // // t1 { + // // ... on T1 { + // // x + // // } + // // ... on T2 { + // // ... Inner + // // } + // // ... on T3 { + // // ... Inner + // // } + // // } + // // } + // // and so `Inner` will not be expanded (it's used twice). Except that + // // the `normalize` code is apply then and will _remove_ both instances + // // of `.... Inner`. Which is ok, but we must make sure the fragment + // // itself is removed since it is not used now, which this test ensures. + // const optimized = withoutFragments.optimize(operation.fragments!, 2); + // expect(validate(gqlSchema, parse(optimized.toString()))).toStrictEqual([]); + // + // expect(optimized.toString()).toMatchString(` + // { + // t1 { + // x + // } + // } + // `); + // }); +} + +#[test] +fn removes_fragments_only_used_by_unused_fragments() { + //test('does not leave fragments only used by unused fragments', () => { + // // Similar to the previous test, but we artificially add a + // // fragment that is only used by the fragment that is finally + // // unused. + // + // const schema = parseSchema(` + // type Query { + // t1: T1 + // } + // + // union U1 = T1 | T2 | T3 + // union U2 = T2 | T3 + // + // type T1 { + // x: Int + // } + // + // type T2 { + // y1: Y + // y2: Y + // } + // + // type T3 { + // z: Int + // } + // + // type Y { + // v: Int + // } + // `); + // const gqlSchema = schema.toGraphQLJSSchema(); + // + // const operation = parseOperation(schema, ` + // query { + // t1 { + // ...Outer + // } + // } + // + // fragment Outer on U1 { + // ... on T1 { + // x + // } + // ... on T2 { + // ... Inner + // } + // ... on T3 { + // ... Inner + // } + // } + // + // fragment Inner on U2 { + // ... on T2 { + // y1 { + // ...WillBeUnused + // } + // y2 { + // ...WillBeUnused + // } + // } + // } + // + // fragment WillBeUnused on Y { + // v + // } + // `); + // expect(validate(gqlSchema, parse(operation.toString()))).toStrictEqual([]); + // + // const withoutFragments = operation.expandAllFragments(); + // expect(withoutFragments.toString()).toMatchString(` + // { + // t1 { + // x + // } + // } + // `); + // + // const optimized = withoutFragments.optimize(operation.fragments!, 2); + // expect(validate(gqlSchema, parse(optimized.toString()))).toStrictEqual([]); + // + // expect(optimized.toString()).toMatchString(` + // { + // t1 { + // x + // } + // } + // `); + // }); +} + +#[test] +fn keeps_fragments_used_by_other_fragments() { + // test('keeps fragments only used by other fragments (if they are used enough times)', () => { + // const schema = parseSchema(` + // type Query { + // t1: T + // t2: T + // } + // + // type T { + // a1: Int + // a2: Int + // b1: B + // b2: B + // } + // + // type B { + // x: Int + // y: Int + // } + // `); + // const gqlSchema = schema.toGraphQLJSSchema(); + // + // const operation = parseOperation(schema, ` + // query { + // t1 { + // ...TFields + // } + // t2 { + // ...TFields + // } + // } + // + // fragment TFields on T { + // ...DirectFieldsOfT + // b1 { + // ...BFields + // } + // b2 { + // ...BFields + // } + // } + // + // fragment DirectFieldsOfT on T { + // a1 + // a2 + // } + // + // fragment BFields on B { + // x + // y + // } + // `); + // expect(validate(gqlSchema, parse(operation.toString()))).toStrictEqual([]); + // + // const withoutFragments = operation.expandAllFragments(); + // expect(withoutFragments.toString()).toMatchString(` + // { + // t1 { + // a1 + // a2 + // b1 { + // x + // y + // } + // b2 { + // x + // y + // } + // } + // t2 { + // a1 + // a2 + // b1 { + // x + // y + // } + // b2 { + // x + // y + // } + // } + // } + // `); + // + // const optimized = withoutFragments.optimize(operation.fragments!, 2); + // expect(validate(gqlSchema, parse(optimized.toString()))).toStrictEqual([]); + // + // // The `DirectFieldsOfT` fragments should not be kept as it is used only once within `TFields`, + // // but the `BFields` one should be kept. + // expect(optimized.toString()).toMatchString(` + // fragment BFields on B { + // x + // y + // } + // + // fragment TFields on T { + // a1 + // a2 + // b1 { + // ...BFields + // } + // b2 { + // ...BFields + // } + // } + // + // { + // t1 { + // ...TFields + // } + // t2 { + // ...TFields + // } + // } + // `); + // }); +} + +/// +/// applied directives +/// + +#[test] +fn reuse_fragments_with_same_directive_on_the_fragment() { + // test('reuse fragments with directives on the fragment, but only when there is those directives', () => { + // const schema = parseSchema(` + // type Query { + // t1: T + // t2: T + // t3: T + // } + // + // type T { + // a: Int + // b: Int + // c: Int + // d: Int + // } + // `); + // + // testFragmentsRoundtrip({ + // schema, + // query: ` + // fragment DirectiveOnDef on T @include(if: $cond1) { + // a + // } + // + // query myQuery($cond1: Boolean!, $cond2: Boolean!) { + // t1 { + // ...DirectiveOnDef + // } + // t2 { + // ... on T @include(if: $cond2) { + // a + // } + // } + // t3 { + // ...DirectiveOnDef @include(if: $cond2) + // } + // } + // `, + // expanded: ` + // query myQuery($cond1: Boolean!, $cond2: Boolean!) { + // t1 { + // ... on T @include(if: $cond1) { + // a + // } + // } + // t2 { + // ... on T @include(if: $cond2) { + // a + // } + // } + // t3 { + // ... on T @include(if: $cond1) @include(if: $cond2) { + // a + // } + // } + // } + // `, + // }); + // }); +} + +#[test] +fn reuse_fragments_with_same_directive_in_the_fragment_selection() { + //test('reuse fragments with directives in the fragment selection, but only when there is those directives', () => { + // const schema = parseSchema(` + // type Query { + // t1: T + // t2: T + // t3: T + // } + // + // type T { + // a: Int + // b: Int + // c: Int + // d: Int + // } + // `); + // + // testFragmentsRoundtrip({ + // schema, + // query: ` + // fragment DirectiveInDef on T { + // a @include(if: $cond1) + // } + // + // query myQuery($cond1: Boolean!, $cond2: Boolean!) { + // t1 { + // a + // } + // t2 { + // ...DirectiveInDef + // } + // t3 { + // a @include(if: $cond2) + // } + // } + // `, + // expanded: ` + // query myQuery($cond1: Boolean!, $cond2: Boolean!) { + // t1 { + // a + // } + // t2 { + // a @include(if: $cond1) + // } + // t3 { + // a @include(if: $cond2) + // } + // } + // `, + // }); + // }); +} + +#[test] +fn reuse_fragments_with_directives_on_inline_fragments() { + //test('reuse fragments with directives on spread, but only when there is those directives', () => { + // const schema = parseSchema(` + // type Query { + // t1: T + // t2: T + // t3: T + // } + // + // type T { + // a: Int + // b: Int + // c: Int + // d: Int + // } + // `); + // + // testFragmentsRoundtrip({ + // schema, + // query: ` + // fragment NoDirectiveDef on T { + // a + // } + // + // query myQuery($cond1: Boolean!) { + // t1 { + // ...NoDirectiveDef + // } + // t2 { + // ...NoDirectiveDef @include(if: $cond1) + // } + // } + // `, + // expanded: ` + // query myQuery($cond1: Boolean!) { + // t1 { + // a + // } + // t2 { + // ... on T @include(if: $cond1) { + // a + // } + // } + // } + // `, + // }); + // }); +} + +/// +/// empty branches removal +/// + +#[test] +fn operation_not_modified_if_no_empty_branches() { + // it.each([ + // '{ t { a } }', + // '{ t { a b } }', + // '{ t { a c { x y } } }', + // ])('is identity if there is no empty branch', (op) => { + // expect(withoutEmptyBranches(op)).toBe(op); + // }); +} + +#[test] +fn removes_simple_empty_branches() { + //it('removes simple empty branches', () => { + // expect(withoutEmptyBranches( + // astSSet( + // astField('t', astSSet( + // astField('a'), + // astField('c', astSSet()), + // )) + // ) + // )).toBe('{ t { a } }'); + // + // expect(withoutEmptyBranches( + // astSSet( + // astField('t', astSSet( + // astField('c', astSSet()), + // astField('a'), + // )) + // ) + // )).toBe('{ t { a } }'); + // + // expect(withoutEmptyBranches( + // astSSet( + // astField('t', astSSet()) + // ) + // )).toBeUndefined(); + // }); +} + +#[test] +fn removes_cascading_empty_branches() { + //it('removes cascading empty branches', () => { + // expect(withoutEmptyBranches( + // astSSet( + // astField('t', astSSet( + // astField('c', astSSet()), + // )) + // ) + // )).toBeUndefined(); + // + // expect(withoutEmptyBranches( + // astSSet( + // astField('u'), + // astField('t', astSSet( + // astField('c', astSSet()), + // )) + // ) + // )).toBe('{ u }'); + // + // expect(withoutEmptyBranches( + // astSSet( + // astField('t', astSSet( + // astField('c', astSSet()), + // )), + // astField('u'), + // ) + // )).toBe('{ u }'); + // }); +} diff --git a/apollo-federation/tests/query_plan/operation_validations_tests.rs b/apollo-federation/tests/query_plan/operation_validations_tests.rs new file mode 100644 index 0000000000..32b610d337 --- /dev/null +++ b/apollo-federation/tests/query_plan/operation_validations_tests.rs @@ -0,0 +1,1013 @@ +/// +/// validations +/// + +#[test] +fn reject_defer_on_mutation() { + //test.each([ + // { directive: '@defer', rootKind: 'mutation' }, + // { directive: '@defer', rootKind: 'subscription' }, + // { directive: '@stream', rootKind: 'mutation' }, + // { directive: '@stream', rootKind: 'subscription' }, + // ])('reject $directive on $rootKind type', ({ directive, rootKind }) => { + // const schema = parseSchema(` + // type Query { + // x: String + // } + // + // type Mutation { + // x: String + // } + // + // type Subscription { + // x: String + // } + // `); + // + // expect(() => { + // parseOperation(schema, ` + // ${rootKind} { + // ... ${directive} { + // x + // } + // } + // `) + // }).toThrowError(new GraphQLError(`The @defer and @stream directives cannot be used on ${rootKind} root type "${defaultRootName(rootKind as SchemaRootKind)}"`)); + // }); +} + +#[test] +fn reject_defer_on_subscription() { + // see reject_defer_on_mutation +} + +#[test] +fn reject_stream_on_mutation() { + // see reject_defer_on_mutation +} + +#[test] +fn reject_stream_on_subscription() { + //// see reject_defer_on_mutation +} + +/// +/// conflicts +/// + +#[test] +fn conflict_between_selection_and_reused_fragment() { + //test('due to conflict between selection and reused fragment', () => { + // const schema = parseSchema(` + // type Query { + // t1: T1 + // i: I + // } + // + // interface I { + // id: ID! + // } + // + // interface WithF { + // f(arg: Int): Int + // } + // + // type T1 implements I { + // id: ID! + // f(arg: Int): Int + // } + // + // type T2 implements I & WithF { + // id: ID! + // f(arg: Int): Int + // } + // `); + // const gqlSchema = schema.toGraphQLJSSchema(); + // + // const operation = parseOperation(schema, ` + // query { + // t1 { + // id + // f(arg: 0) + // } + // i { + // ...F1 + // } + // } + // + // fragment F1 on I { + // id + // ... on WithF { + // f(arg: 1) + // } + // } + // `); + // expect(validate(gqlSchema, parse(operation.toString()))).toStrictEqual([]); + // + // const withoutFragments = operation.expandAllFragments(); + // expect(withoutFragments.toString()).toMatchString(` + // { + // t1 { + // id + // f(arg: 0) + // } + // i { + // id + // ... on WithF { + // f(arg: 1) + // } + // } + // } + // `); + // + // // Note that technically, `t1` has return type `T1` which is a `I`, so `F1` can be spread + // // within `t1`, and `t1 { ...F1 }` is just `t1 { id }` (because `T!` does not implement `WithF`), + // // so that it would appear that it could be valid to optimize this query into: + // // { + // // t1 { + // // ...F1 // Notice the use of F1 here, which does expand to `id` in this context + // // f(arg: 0) + // // } + // // i { + // // ...F1 + // // } + // // } + // // And while doing this may look "dumb" in that toy example (we're replacing `id` with `...F1` + // // which is longer so less optimal really), it's easy to expand this to example where re-using + // // `F1` this way _does_ make things smaller. + // // + // // But the query above is actually invalid. And it is invalid because the validation of graphQL + // // does not take into account the fact that the `... on WithF` part of `F1` is basically dead + // // code within `t1`. And so it finds a conflict between `f(arg: 0)` and the `f(arg: 1)` in `F1` + // // (even though, again, the later is statically known to never apply, but graphQL does not + // // include such static analysis in its validation). + // // + // // And so this test does make sure we do not generate the query above (do not use `F1` in `t1`). + // const optimized = withoutFragments.optimize(operation.fragments!, 1); + // expect(validate(gqlSchema, parse(optimized.toString()))).toStrictEqual([]); + // + // expect(optimized.toString()).toMatchString(` + // fragment F1 on I { + // id + // ... on WithF { + // f(arg: 1) + // } + // } + // + // { + // t1 { + // id + // f(arg: 0) + // } + // i { + // ...F1 + // } + // } + // `); + // }); +} + +#[test] +fn conflict_between_reused_fragment_and_another_trimmed_fragment() { + //test('due to conflict between the active selection of a reused fragment and the trimmed part of another fragments', () => { + // const schema = parseSchema(` + // type Query { + // t1: T1 + // i: I + // } + // + // interface I { + // id: ID! + // } + // + // interface WithF { + // f(arg: Int): Int + // } + // + // type T1 implements I { + // id: ID! + // f(arg: Int): Int + // } + // + // type T2 implements I & WithF { + // id: ID! + // f(arg: Int): Int + // } + // `); + // const gqlSchema = schema.toGraphQLJSSchema(); + // + // const operation = parseOperation(schema, ` + // query { + // t1 { + // id + // ...F1 + // } + // i { + // ...F2 + // } + // } + // + // fragment F1 on T1 { + // f(arg: 0) + // } + // + // fragment F2 on I { + // id + // ... on WithF { + // f(arg: 1) + // } + // } + // + // `); + // expect(validate(gqlSchema, parse(operation.toString()))).toStrictEqual([]); + // + // const withoutFragments = operation.expandAllFragments(); + // expect(withoutFragments.toString()).toMatchString(` + // { + // t1 { + // id + // f(arg: 0) + // } + // i { + // id + // ... on WithF { + // f(arg: 1) + // } + // } + // } + // `); + // + // // See the comments on the previous test. The only different here is that `F1` is applied + // // first, and then we need to make sure we do not apply `F2` even though it's restriction + // // inside `t1` matches its selection set. + // const optimized = withoutFragments.optimize(operation.fragments!, 1); + // expect(validate(gqlSchema, parse(optimized.toString()))).toStrictEqual([]); + // + // expect(optimized.toString()).toMatchString(` + // fragment F1 on T1 { + // f(arg: 0) + // } + // + // fragment F2 on I { + // id + // ... on WithF { + // f(arg: 1) + // } + // } + // + // { + // t1 { + // ...F1 + // id + // } + // i { + // ...F2 + // } + // } + // `); + // }); +} + +#[test] +fn conflict_between_trimmed_parts_of_two_fragments() { + //test('due to conflict between the trimmed parts of 2 fragments', () => { + // const schema = parseSchema(` + // type Query { + // t1: T1 + // i1: I + // i2: I + // } + // + // interface I { + // id: ID! + // a: Int + // b: Int + // } + // + // interface WithF { + // f(arg: Int): Int + // } + // + // type T1 implements I { + // id: ID! + // a: Int + // b: Int + // f(arg: Int): Int + // } + // + // type T2 implements I & WithF { + // id: ID! + // a: Int + // b: Int + // f(arg: Int): Int + // } + // `); + // const gqlSchema = schema.toGraphQLJSSchema(); + // + // const operation = parseOperation(schema, ` + // query { + // t1 { + // id + // a + // b + // } + // i1 { + // ...F1 + // } + // i2 { + // ...F2 + // } + // } + // + // fragment F1 on I { + // id + // a + // ... on WithF { + // f(arg: 0) + // } + // } + // + // fragment F2 on I { + // id + // b + // ... on WithF { + // f(arg: 1) + // } + // } + // + // `); + // expect(validate(gqlSchema, parse(operation.toString()))).toStrictEqual([]); + // + // const withoutFragments = operation.expandAllFragments(); + // expect(withoutFragments.toString()).toMatchString(` + // { + // t1 { + // id + // a + // b + // } + // i1 { + // id + // a + // ... on WithF { + // f(arg: 0) + // } + // } + // i2 { + // id + // b + // ... on WithF { + // f(arg: 1) + // } + // } + // } + // `); + // + // // Here, `F1` in `T1` reduces to `{ id a }` and F2 reduces to `{ id b }`, so theoretically both could be used + // // within the first `T1` branch. But they can't both be used because their `... on WithF` part conflict, + // // and even though that part is dead in `T1`, this would still be illegal graphQL. + // const optimized = withoutFragments.optimize(operation.fragments!, 1); + // expect(validate(gqlSchema, parse(optimized.toString()))).toStrictEqual([]); + // + // expect(optimized.toString()).toMatchString(` + // fragment F1 on I { + // id + // a + // ... on WithF { + // f(arg: 0) + // } + // } + // + // fragment F2 on I { + // id + // b + // ... on WithF { + // f(arg: 1) + // } + // } + // + // { + // t1 { + // ...F1 + // b + // } + // i1 { + // ...F1 + // } + // i2 { + // ...F2 + // } + // } + // `); + // }); +} + +#[test] +fn conflict_between_selection_and_reused_fragment_at_different_level() { + // test('due to conflict between selection and reused fragment at different levels', () => { + // const schema = parseSchema(` + // type Query { + // t1: SomeV + // t2: SomeV + // } + // + // union SomeV = V1 | V2 | V3 + // + // type V1 { + // x: String + // } + // + // type V2 { + // y: String! + // } + // + // type V3 { + // x: Int + // } + // `); + // const gqlSchema = schema.toGraphQLJSSchema(); + // + // const operation = parseOperation(schema, ` + // fragment onV1V2 on SomeV { + // ... on V1 { + // x + // } + // ... on V2 { + // y + // } + // } + // + // query { + // t1 { + // ...onV1V2 + // } + // t2 { + // ... on V2 { + // y + // } + // ... on V3 { + // x + // } + // } + // } + // `); + // expect(validate(gqlSchema, parse(operation.toString()))).toStrictEqual([]); + // + // const withoutFragments = operation.expandAllFragments(); + // expect(withoutFragments.toString()).toMatchString(` + // { + // t1 { + // ... on V1 { + // x + // } + // ... on V2 { + // y + // } + // } + // t2 { + // ... on V2 { + // y + // } + // ... on V3 { + // x + // } + // } + // } + // `); + // + // const optimized = withoutFragments.optimize(operation.fragments!, 1); + // expect(validate(gqlSchema, parse(optimized.toString()))).toStrictEqual([]); + // + // expect(optimized.toString()).toMatchString(` + // fragment onV1V2 on SomeV { + // ... on V1 { + // x + // } + // ... on V2 { + // y + // } + // } + // + // { + // t1 { + // ...onV1V2 + // } + // t2 { + // ... on V2 { + // y + // } + // ... on V3 { + // x + // } + // } + // } + // `); + // }); +} + +#[test] +fn conflict_between_fragments_at_different_levels() { + //test('due to conflict between the trimmed parts of 2 fragments at different levels', () => { + // const schema = parseSchema(` + // type Query { + // t1: SomeV + // t2: SomeV + // t3: OtherV + // } + // + // union SomeV = V1 | V2 | V3 + // union OtherV = V3 + // + // type V1 { + // x: String + // } + // + // type V2 { + // x: Int + // } + // + // type V3 { + // y: String! + // z: String! + // } + // `); + // const gqlSchema = schema.toGraphQLJSSchema(); + // + // const operation = parseOperation(schema, ` + // fragment onV1V3 on SomeV { + // ... on V1 { + // x + // } + // ... on V3 { + // y + // } + // } + // + // fragment onV2V3 on SomeV { + // ... on V2 { + // x + // } + // ... on V3 { + // z + // } + // } + // + // query { + // t1 { + // ...onV1V3 + // } + // t2 { + // ...onV2V3 + // } + // t3 { + // ... on V3 { + // y + // z + // } + // } + // } + // `); + // expect(validate(gqlSchema, parse(operation.toString()))).toStrictEqual([]); + // + // const withoutFragments = operation.expandAllFragments(); + // expect(withoutFragments.toString()).toMatchString(` + // { + // t1 { + // ... on V1 { + // x + // } + // ... on V3 { + // y + // } + // } + // t2 { + // ... on V2 { + // x + // } + // ... on V3 { + // z + // } + // } + // t3 { + // ... on V3 { + // y + // z + // } + // } + // } + // `); + // + // const optimized = withoutFragments.optimize(operation.fragments!, 1); + // expect(validate(gqlSchema, parse(optimized.toString()))).toStrictEqual([]); + // + // expect(optimized.toString()).toMatchString(` + // fragment onV1V3 on SomeV { + // ... on V1 { + // x + // } + // ... on V3 { + // y + // } + // } + // + // fragment onV2V3 on SomeV { + // ... on V2 { + // x + // } + // ... on V3 { + // z + // } + // } + // + // { + // t1 { + // ...onV1V3 + // } + // t2 { + // ...onV2V3 + // } + // t3 { + // ...onV1V3 + // ... on V3 { + // z + // } + // } + // } + // `); + // }); +} + +#[test] +fn conflict_between_two_sibling_branches() { + // test('due to conflict between 2 sibling branches', () => { + // const schema = parseSchema(` + // type Query { + // t1: SomeV + // i: I + // } + // + // interface I { + // id: ID! + // } + // + // type T1 implements I { + // id: ID! + // t2: SomeV + // } + // + // type T2 implements I { + // id: ID! + // t2: SomeV + // } + // + // union SomeV = V1 | V2 | V3 + // + // type V1 { + // x: String + // } + // + // type V2 { + // y: String! + // } + // + // type V3 { + // x: Int + // } + // `); + // const gqlSchema = schema.toGraphQLJSSchema(); + // + // const operation = parseOperation(schema, ` + // fragment onV1V2 on SomeV { + // ... on V1 { + // x + // } + // ... on V2 { + // y + // } + // } + // + // query { + // t1 { + // ...onV1V2 + // } + // i { + // ... on T1 { + // t2 { + // ... on V2 { + // y + // } + // } + // } + // ... on T2 { + // t2 { + // ... on V3 { + // x + // } + // } + // } + // } + // } + // `); + // expect(validate(gqlSchema, parse(operation.toString()))).toStrictEqual([]); + // + // const withoutFragments = operation.expandAllFragments(); + // expect(withoutFragments.toString()).toMatchString(` + // { + // t1 { + // ... on V1 { + // x + // } + // ... on V2 { + // y + // } + // } + // i { + // ... on T1 { + // t2 { + // ... on V2 { + // y + // } + // } + // } + // ... on T2 { + // t2 { + // ... on V3 { + // x + // } + // } + // } + // } + // } + // `); + // + // const optimized = withoutFragments.optimize(operation.fragments!, 1); + // expect(validate(gqlSchema, parse(optimized.toString()))).toStrictEqual([]); + // + // expect(optimized.toString()).toMatchString(` + // fragment onV1V2 on SomeV { + // ... on V1 { + // x + // } + // ... on V2 { + // y + // } + // } + // + // { + // t1 { + // ...onV1V2 + // } + // i { + // ... on T1 { + // t2 { + // ... on V2 { + // y + // } + // } + // } + // ... on T2 { + // t2 { + // ... on V3 { + // x + // } + // } + // } + // } + // } + // `); + // }); +} + +#[test] +fn conflict_when_inline_fragment_should_be_normalized() { + // test('when a spread inside an expanded fragment should be "normalized away"', () => { + // const schema = parseSchema(` + // type Query { + // t1: T1 + // i: I + // } + // + // interface I { + // id: ID! + // } + // + // type T1 implements I { + // id: ID! + // a: Int + // } + // + // type T2 implements I { + // id: ID! + // b: Int + // c: Int + // } + // `); + // const gqlSchema = schema.toGraphQLJSSchema(); + // + // const operation = parseOperation(schema, ` + // { + // t1 { + // ...GetAll + // } + // i { + // ...GetT2 + // } + // } + // + // fragment GetAll on I { + // ... on T1 { + // a + // } + // ...GetT2 + // ... on T2 { + // c + // } + // } + // + // fragment GetT2 on T2 { + // b + // } + // `); + // expect(validate(gqlSchema, parse(operation.toString()))).toStrictEqual([]); + // + // const withoutFragments = operation.expandAllFragments(); + // expect(withoutFragments.toString()).toMatchString(` + // { + // t1 { + // a + // } + // i { + // ... on T2 { + // b + // } + // } + // } + // `); + // + // // As we re-optimize, we will initially generated the initial query. But + // // as we ask to only optimize fragments used more than once, the `GetAll` + // // fragment will be re-expanded (`GetT2` will not because the code will say + // // that it is used both in the expanded `GetAll` but also inside `i`). + // // But because `GetAll` is within `t1: T1`, that expansion should actually + // // get rid of anything `T2`-related. + // // This test exists because a previous version of the code was not correctly + // // "getting rid" of the `...GetT2` spread, keeping in the query, which is + // // invalid (we cannot have `...GetT2` inside `t1`). + // const optimized = withoutFragments.optimize(operation.fragments!, 2); + // expect(validate(gqlSchema, parse(optimized.toString()))).toStrictEqual([]); + // + // expect(optimized.toString()).toMatchString(` + // fragment GetT2 on T2 { + // b + // } + // + // { + // t1 { + // a + // } + // i { + // ...GetT2 + // } + // } + // `); + // }); +} + +#[test] +fn conflict_due_to_trimmed_selections_of_nested_fragments() { + //test('due to the trimmed selection of nested fragments', () => { + // const schema = parseSchema(` + // type Query { + // u1: U + // u2: U + // u3: U + // } + // + // union U = S | T + // + // type T { + // id: ID! + // vt: Int + // } + // + // interface I { + // vs: Int + // } + // + // type S implements I { + // vs: Int! + // } + // `); + // const gqlSchema = schema.toGraphQLJSSchema(); + // + // const operation = parseOperation(schema, ` + // { + // u1 { + // ...F1 + // } + // u2 { + // ...F3 + // } + // u3 { + // ...F3 + // } + // } + // + // fragment F1 on U { + // ... on S { + // __typename + // vs + // } + // ... on T { + // __typename + // vt + // } + // } + // + // fragment F2 on T { + // __typename + // vt + // } + // + // fragment F3 on U { + // ... on I { + // vs + // } + // ...F2 + // } + // `); + // expect(validate(gqlSchema, parse(operation.toString()))).toStrictEqual([]); + // + // const withoutFragments = operation.expandAllFragments(); + // expect(withoutFragments.toString()).toMatchString(` + // { + // u1 { + // ... on S { + // __typename + // vs + // } + // ... on T { + // __typename + // vt + // } + // } + // u2 { + // ... on I { + // vs + // } + // ... on T { + // __typename + // vt + // } + // } + // u3 { + // ... on I { + // vs + // } + // ... on T { + // __typename + // vt + // } + // } + // } + // `); + // + // // We use `mapToExpandedSelectionSets` with a no-op mapper because this will still expand the selections + // // and re-optimize them, which 1) happens to match what happens in the query planner and 2) is necessary + // // for reproducing a bug that this test was initially added to cover. + // const newFragments = operation.fragments!.mapToExpandedSelectionSets((s) => s); + // const optimized = withoutFragments.optimize(newFragments, 2); + // expect(validate(gqlSchema, parse(optimized.toString()))).toStrictEqual([]); + // + // expect(optimized.toString()).toMatchString(` + // fragment F3 on U { + // ... on I { + // vs + // } + // ... on T { + // __typename + // vt + // } + // } + // + // { + // u1 { + // ... on S { + // __typename + // vs + // } + // ... on T { + // __typename + // vt + // } + // } + // u2 { + // ...F3 + // } + // u3 { + // ...F3 + // } + // } + // `); + // }); +} diff --git a/apollo-federation/tests/query_plan/supergraphs/can_use_same_root_operation_from_multiple_subgraphs_in_parallel.graphql b/apollo-federation/tests/query_plan/supergraphs/can_use_same_root_operation_from_multiple_subgraphs_in_parallel.graphql new file mode 100644 index 0000000000..66e59ae104 --- /dev/null +++ b/apollo-federation/tests/query_plan/supergraphs/can_use_same_root_operation_from_multiple_subgraphs_in_parallel.graphql @@ -0,0 +1,58 @@ +# Composed from subgraphs with hash: ef972ffb2fc3cdf19f816cdc10e29756fb3f0656 +schema + @link(url: "https://specs.apollo.dev/link/v1.0") + @link(url: "https://specs.apollo.dev/join/v0.3", for: EXECUTION) +{ + query: Query +} + +directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE + +directive @join__field(graph: join__Graph, requires: join__FieldSet, provides: join__FieldSet, type: String, external: Boolean, override: String, usedOverridden: Boolean) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION + +directive @join__graph(name: String!, url: String!) on ENUM_VALUE + +directive @join__implements(graph: join__Graph!, interface: String!) repeatable on OBJECT | INTERFACE + +directive @join__type(graph: join__Graph!, key: join__FieldSet, extension: Boolean! = false, resolvable: Boolean! = true, isInterfaceObject: Boolean! = false) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR + +directive @join__unionMember(graph: join__Graph!, member: String!) repeatable on UNION + +directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA + +scalar join__FieldSet + +enum join__Graph { + SUBGRAPH1 @join__graph(name: "Subgraph1", url: "none") + SUBGRAPH2 @join__graph(name: "Subgraph2", url: "none") +} + +scalar link__Import + +enum link__Purpose { + """ + `SECURITY` features provide metadata necessary to securely resolve fields. + """ + SECURITY + + """ + `EXECUTION` features provide metadata necessary for operation execution. + """ + EXECUTION +} + +type Query + @join__type(graph: SUBGRAPH1) + @join__type(graph: SUBGRAPH2) +{ + me: User! +} + +type User + @join__type(graph: SUBGRAPH1, key: "id") + @join__type(graph: SUBGRAPH2, key: "id") +{ + id: ID! + prop1: String @join__field(graph: SUBGRAPH1) + prop2: String @join__field(graph: SUBGRAPH2) +} diff --git a/apollo-federation/tests/query_plan/supergraphs/handles_root_operation_shareable_in_many_subgraphs.graphql b/apollo-federation/tests/query_plan/supergraphs/handles_root_operation_shareable_in_many_subgraphs.graphql new file mode 100644 index 0000000000..76ccd6875f --- /dev/null +++ b/apollo-federation/tests/query_plan/supergraphs/handles_root_operation_shareable_in_many_subgraphs.graphql @@ -0,0 +1,63 @@ +# Composed from subgraphs with hash: 23796fabaf5b788d4a3df5cd4043263f2d825828 +schema + @link(url: "https://specs.apollo.dev/link/v1.0") + @link(url: "https://specs.apollo.dev/join/v0.3", for: EXECUTION) +{ + query: Query +} + +directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE + +directive @join__field(graph: join__Graph, requires: join__FieldSet, provides: join__FieldSet, type: String, external: Boolean, override: String, usedOverridden: Boolean) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION + +directive @join__graph(name: String!, url: String!) on ENUM_VALUE + +directive @join__implements(graph: join__Graph!, interface: String!) repeatable on OBJECT | INTERFACE + +directive @join__type(graph: join__Graph!, key: join__FieldSet, extension: Boolean! = false, resolvable: Boolean! = true, isInterfaceObject: Boolean! = false) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR + +directive @join__unionMember(graph: join__Graph!, member: String!) repeatable on UNION + +directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA + +scalar join__FieldSet + +enum join__Graph { + SUBGRAPH1 @join__graph(name: "Subgraph1", url: "none") + SUBGRAPH2 @join__graph(name: "Subgraph2", url: "none") + SUBGRAPH3 @join__graph(name: "Subgraph3", url: "none") +} + +scalar link__Import + +enum link__Purpose { + """ + `SECURITY` features provide metadata necessary to securely resolve fields. + """ + SECURITY + + """ + `EXECUTION` features provide metadata necessary for operation execution. + """ + EXECUTION +} + +type Query + @join__type(graph: SUBGRAPH1) + @join__type(graph: SUBGRAPH2) + @join__type(graph: SUBGRAPH3) +{ + me: User! @join__field(graph: SUBGRAPH2) @join__field(graph: SUBGRAPH3) +} + +type User + @join__type(graph: SUBGRAPH1, key: "id") + @join__type(graph: SUBGRAPH2, key: "id") + @join__type(graph: SUBGRAPH3, key: "id") +{ + id: ID! + f0: Int @join__field(graph: SUBGRAPH1) + f1: Int @join__field(graph: SUBGRAPH1) + f2: Int @join__field(graph: SUBGRAPH1) + f3: Int @join__field(graph: SUBGRAPH1) +} diff --git a/apollo-federation/tests/snapshots/main__composition_tests__can_compose_supergraph-2.snap b/apollo-federation/tests/snapshots/main__composition_tests__can_compose_supergraph-2.snap new file mode 100644 index 0000000000..25157aa3ec --- /dev/null +++ b/apollo-federation/tests/snapshots/main__composition_tests__can_compose_supergraph-2.snap @@ -0,0 +1,25 @@ +--- +source: apollo-federation/tests/composition_tests.rs +expression: print_sdl(&supergraph.to_api_schema()) +--- +enum E { + V1 + V2 +} + +type Query { + t: T +} + +type S { + x: Int +} + +type T { + k: ID + a: Int + b: String +} + +union U = S | T + diff --git a/apollo-federation/tests/snapshots/main__composition_tests__can_compose_supergraph.snap b/apollo-federation/tests/snapshots/main__composition_tests__can_compose_supergraph.snap new file mode 100644 index 0000000000..65219fc3d6 --- /dev/null +++ b/apollo-federation/tests/snapshots/main__composition_tests__can_compose_supergraph.snap @@ -0,0 +1,61 @@ +--- +source: tests/composition_tests.rs +expression: print_sdl(&supergraph.schema) +--- +schema @link(url: "https://specs.apollo.dev/link/v1.0") @link(url: "https://specs.apollo.dev/join/v0.3", for: EXECUTION) { + query: Query +} + +directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE + +directive @join__field(graph: join__Graph, requires: join__FieldSet, provides: join__FieldSet, type: String, external: Boolean, override: String, usedOverridden: Boolean) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION + +directive @join__graph(name: String!, url: String!) on ENUM_VALUE + +directive @join__implements(graph: join__Graph!, interface: String!) repeatable on INTERFACE | OBJECT + +directive @join__type(graph: join__Graph!, key: join__FieldSet, extension: Boolean! = false, resolvable: Boolean! = true, isInterfaceObject: Boolean! = false) repeatable on ENUM | INPUT_OBJECT | INTERFACE | OBJECT | SCALAR | UNION + +directive @join__unionMember(graph: join__Graph!, member: String!) repeatable on UNION + +directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA + +enum E @join__type(graph: SUBGRAPH2) { + V1 @join__enumValue(graph: SUBGRAPH2) + V2 @join__enumValue(graph: SUBGRAPH2) +} + +type Query @join__type(graph: SUBGRAPH1) @join__type(graph: SUBGRAPH2) { + t: T @join__field(graph: SUBGRAPH1) +} + +type S @join__type(graph: SUBGRAPH1) { + x: Int +} + +type T @join__type(graph: SUBGRAPH1, key: "k") @join__type(graph: SUBGRAPH2, key: "k") { + k: ID + a: Int @join__field(graph: SUBGRAPH2) + b: String @join__field(graph: SUBGRAPH2) +} + +union U @join__type(graph: SUBGRAPH1) @join__unionMember(graph: SUBGRAPH1, member: "S") @join__unionMember(graph: SUBGRAPH1, member: "T") = S | T + +scalar join__FieldSet + +enum join__Graph { + SUBGRAPH1 @join__graph(name: "Subgraph1", url: "https://subgraph1") + SUBGRAPH2 @join__graph(name: "Subgraph2", url: "https://subgraph2") +} + +scalar link__Import + +enum link__Purpose { + """ + SECURITY features provide metadata necessary to securely resolve fields. + """ + SECURITY + """EXECUTION features provide metadata necessary for operation execution.""" + EXECUTION +} + diff --git a/apollo-federation/tests/snapshots/main__composition_tests__can_compose_types_from_different_subgraphs-2.snap b/apollo-federation/tests/snapshots/main__composition_tests__can_compose_types_from_different_subgraphs-2.snap new file mode 100644 index 0000000000..a29eec9523 --- /dev/null +++ b/apollo-federation/tests/snapshots/main__composition_tests__can_compose_types_from_different_subgraphs-2.snap @@ -0,0 +1,18 @@ +--- +source: apollo-federation/tests/composition_tests.rs +expression: print_sdl(&supergraph.to_api_schema()) +--- +type Product { + sku: String! + name: String! +} + +type Query { + products: [Product!] +} + +type User { + name: String + email: String! +} + diff --git a/apollo-federation/tests/snapshots/main__composition_tests__can_compose_types_from_different_subgraphs.snap b/apollo-federation/tests/snapshots/main__composition_tests__can_compose_types_from_different_subgraphs.snap new file mode 100644 index 0000000000..85bdfb6be8 --- /dev/null +++ b/apollo-federation/tests/snapshots/main__composition_tests__can_compose_types_from_different_subgraphs.snap @@ -0,0 +1,54 @@ +--- +source: tests/composition_tests.rs +expression: print_sdl(&supergraph.schema) +--- +schema @link(url: "https://specs.apollo.dev/link/v1.0") @link(url: "https://specs.apollo.dev/join/v0.3", for: EXECUTION) { + query: Query +} + +directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE + +directive @join__field(graph: join__Graph, requires: join__FieldSet, provides: join__FieldSet, type: String, external: Boolean, override: String, usedOverridden: Boolean) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION + +directive @join__graph(name: String!, url: String!) on ENUM_VALUE + +directive @join__implements(graph: join__Graph!, interface: String!) repeatable on INTERFACE | OBJECT + +directive @join__type(graph: join__Graph!, key: join__FieldSet, extension: Boolean! = false, resolvable: Boolean! = true, isInterfaceObject: Boolean! = false) repeatable on ENUM | INPUT_OBJECT | INTERFACE | OBJECT | SCALAR | UNION + +directive @join__unionMember(graph: join__Graph!, member: String!) repeatable on UNION + +directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA + +type Product @join__type(graph: SUBGRAPHA) { + sku: String! + name: String! +} + +type Query @join__type(graph: SUBGRAPHA) @join__type(graph: SUBGRAPHB) { + products: [Product!] @join__field(graph: SUBGRAPHA) +} + +type User @join__type(graph: SUBGRAPHB) { + name: String + email: String! +} + +scalar join__FieldSet + +enum join__Graph { + SUBGRAPHA @join__graph(name: "SubgraphA", url: "https://subgraphA") + SUBGRAPHB @join__graph(name: "SubgraphB", url: "https://subgraphB") +} + +scalar link__Import + +enum link__Purpose { + """ + SECURITY features provide metadata necessary to securely resolve fields. + """ + SECURITY + """EXECUTION features provide metadata necessary for operation execution.""" + EXECUTION +} + diff --git a/apollo-federation/tests/snapshots/main__composition_tests__can_compose_with_descriptions-2.snap b/apollo-federation/tests/snapshots/main__composition_tests__can_compose_with_descriptions-2.snap new file mode 100644 index 0000000000..4f16ebe955 --- /dev/null +++ b/apollo-federation/tests/snapshots/main__composition_tests__can_compose_with_descriptions-2.snap @@ -0,0 +1,32 @@ +--- +source: tests/composition_tests.rs +expression: print_sdl(&supergraph.to_api_schema()) +--- +"""A cool schema""" +schema { + query: Query +} + +"""The foo directive description""" +directive @foo(url: String) on FIELD + +"""An enum""" +enum E { + """The A value""" + A + """The B value""" + B +} + +""" +Available queries +Not much yet +""" +type Query { + """Returns tea""" + t( + """An argument that is very important""" + x: String!, + ): String +} + diff --git a/apollo-federation/tests/snapshots/main__composition_tests__can_compose_with_descriptions.snap b/apollo-federation/tests/snapshots/main__composition_tests__can_compose_with_descriptions.snap new file mode 100644 index 0000000000..5f5b33458d --- /dev/null +++ b/apollo-federation/tests/snapshots/main__composition_tests__can_compose_with_descriptions.snap @@ -0,0 +1,64 @@ +--- +source: tests/composition_tests.rs +expression: print_sdl(&supergraph.schema) +--- +"""A cool schema""" +schema @link(url: "https://specs.apollo.dev/link/v1.0") @link(url: "https://specs.apollo.dev/join/v0.3", for: EXECUTION) { + query: Query +} + +"""The foo directive description""" +directive @foo(url: String) on FIELD + +directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE + +directive @join__field(graph: join__Graph, requires: join__FieldSet, provides: join__FieldSet, type: String, external: Boolean, override: String, usedOverridden: Boolean) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION + +directive @join__graph(name: String!, url: String!) on ENUM_VALUE + +directive @join__implements(graph: join__Graph!, interface: String!) repeatable on INTERFACE | OBJECT + +directive @join__type(graph: join__Graph!, key: join__FieldSet, extension: Boolean! = false, resolvable: Boolean! = true, isInterfaceObject: Boolean! = false) repeatable on ENUM | INPUT_OBJECT | INTERFACE | OBJECT | SCALAR | UNION + +directive @join__unionMember(graph: join__Graph!, member: String!) repeatable on UNION + +directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA + +"""An enum""" +enum E @join__type(graph: SUBGRAPH2) { + """The A value""" + A @join__enumValue(graph: SUBGRAPH2) + """The B value""" + B @join__enumValue(graph: SUBGRAPH2) +} + +""" +Available queries +Not much yet +""" +type Query @join__type(graph: SUBGRAPH1) @join__type(graph: SUBGRAPH2) { + """Returns tea""" + t( + """An argument that is very important""" + x: String!, + ): String @join__field(graph: SUBGRAPH1) +} + +scalar join__FieldSet + +enum join__Graph { + SUBGRAPH1 @join__graph(name: "Subgraph1", url: "https://subgraph1") + SUBGRAPH2 @join__graph(name: "Subgraph2", url: "https://subgraph2") +} + +scalar link__Import + +enum link__Purpose { + """ + SECURITY features provide metadata necessary to securely resolve fields. + """ + SECURITY + """EXECUTION features provide metadata necessary for operation execution.""" + EXECUTION +} + diff --git a/apollo-federation/tests/snapshots/main__composition_tests__compose_removes_federation_directives-2.snap b/apollo-federation/tests/snapshots/main__composition_tests__compose_removes_federation_directives-2.snap new file mode 100644 index 0000000000..02e107db45 --- /dev/null +++ b/apollo-federation/tests/snapshots/main__composition_tests__compose_removes_federation_directives-2.snap @@ -0,0 +1,13 @@ +--- +source: apollo-federation/tests/composition_tests.rs +expression: print_sdl(&supergraph.to_api_schema()) +--- +type Product { + sku: String! + name: String! +} + +type Query { + products: [Product!] +} + diff --git a/apollo-federation/tests/snapshots/main__composition_tests__compose_removes_federation_directives.snap b/apollo-federation/tests/snapshots/main__composition_tests__compose_removes_federation_directives.snap new file mode 100644 index 0000000000..d82002e9b4 --- /dev/null +++ b/apollo-federation/tests/snapshots/main__composition_tests__compose_removes_federation_directives.snap @@ -0,0 +1,49 @@ +--- +source: tests/composition_tests.rs +expression: print_sdl(&supergraph.schema) +--- +schema @link(url: "https://specs.apollo.dev/link/v1.0") @link(url: "https://specs.apollo.dev/join/v0.3", for: EXECUTION) { + query: Query +} + +directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE + +directive @join__field(graph: join__Graph, requires: join__FieldSet, provides: join__FieldSet, type: String, external: Boolean, override: String, usedOverridden: Boolean) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION + +directive @join__graph(name: String!, url: String!) on ENUM_VALUE + +directive @join__implements(graph: join__Graph!, interface: String!) repeatable on INTERFACE | OBJECT + +directive @join__type(graph: join__Graph!, key: join__FieldSet, extension: Boolean! = false, resolvable: Boolean! = true, isInterfaceObject: Boolean! = false) repeatable on ENUM | INPUT_OBJECT | INTERFACE | OBJECT | SCALAR | UNION + +directive @join__unionMember(graph: join__Graph!, member: String!) repeatable on UNION + +directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA + +type Product @join__type(graph: SUBGRAPHA, key: "sku") @join__type(graph: SUBGRAPHB, key: "sku") { + sku: String! + name: String! @join__field(graph: SUBGRAPHA, external: true) @join__field(graph: SUBGRAPHB) +} + +type Query @join__type(graph: SUBGRAPHA) @join__type(graph: SUBGRAPHB) { + products: [Product!] @join__field(graph: SUBGRAPHA, provides: "name") +} + +scalar join__FieldSet + +enum join__Graph { + SUBGRAPHA @join__graph(name: "SubgraphA", url: "https://subgraphA") + SUBGRAPHB @join__graph(name: "SubgraphB", url: "https://subgraphB") +} + +scalar link__Import + +enum link__Purpose { + """ + SECURITY features provide metadata necessary to securely resolve fields. + """ + SECURITY + """EXECUTION features provide metadata necessary for operation execution.""" + EXECUTION +} + diff --git a/apollo-federation/tests/snapshots/main__extract_subgraphs__can_extract_subgraph.snap b/apollo-federation/tests/snapshots/main__extract_subgraphs__can_extract_subgraph.snap new file mode 100644 index 0000000000..97609b3e52 --- /dev/null +++ b/apollo-federation/tests/snapshots/main__extract_subgraphs__can_extract_subgraph.snap @@ -0,0 +1,157 @@ +--- +source: tests/extract_subgraphs.rs +expression: snapshot +--- +Subgraph1: https://Subgraph1 +--- +schema { + query: Query +} + +extend schema @link(url: "https://specs.apollo.dev/link/v1.0") @link(url: "https://specs.apollo.dev/federation/v2.5") + +directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA + +directive @federation__key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE + +directive @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION + +directive @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION + +directive @federation__external(reason: String) on OBJECT | FIELD_DEFINITION + +directive @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA + +directive @federation__extends on OBJECT | INTERFACE + +directive @federation__shareable on OBJECT | FIELD_DEFINITION + +directive @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION + +directive @federation__override(from: String!) on FIELD_DEFINITION + +directive @federation__composeDirective(name: String) repeatable on SCHEMA + +directive @federation__interfaceObject on OBJECT + +directive @federation__authenticated on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM + +directive @federation__requiresScopes(scopes: [[federation__Scope!]!]!) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM + +scalar link__Import + +enum link__Purpose { + """ + \`SECURITY\` features provide metadata necessary to securely resolve fields. + """ + SECURITY + """ + \`EXECUTION\` features provide metadata necessary for operation execution. + """ + EXECUTION +} + +scalar federation__FieldSet + +scalar federation__Scope + +type Query { + t: T + _entities(representations: [_Any!]!): [_Entity]! + _service: _Service! +} + +type S { + x: Int +} + +type T @federation__key(fields: "k", resolvable: true) { + k: ID @federation__shareable +} + +union U = S | T + +scalar _Any + +type _Service { + sdl: String +} + +union _Entity = T + +Subgraph2: https://Subgraph2 +--- +schema { + query: Query +} + +extend schema @link(url: "https://specs.apollo.dev/link/v1.0") @link(url: "https://specs.apollo.dev/federation/v2.5") + +directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA + +directive @federation__key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE + +directive @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION + +directive @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION + +directive @federation__external(reason: String) on OBJECT | FIELD_DEFINITION + +directive @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA + +directive @federation__extends on OBJECT | INTERFACE + +directive @federation__shareable on OBJECT | FIELD_DEFINITION + +directive @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION + +directive @federation__override(from: String!) on FIELD_DEFINITION + +directive @federation__composeDirective(name: String) repeatable on SCHEMA + +directive @federation__interfaceObject on OBJECT + +directive @federation__authenticated on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM + +directive @federation__requiresScopes(scopes: [[federation__Scope!]!]!) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM + +scalar link__Import + +enum link__Purpose { + """ + \`SECURITY\` features provide metadata necessary to securely resolve fields. + """ + SECURITY + """ + \`EXECUTION\` features provide metadata necessary for operation execution. + """ + EXECUTION +} + +scalar federation__FieldSet + +scalar federation__Scope + +enum E { + V1 + V2 +} + +type T @federation__key(fields: "k", resolvable: true) { + k: ID @federation__shareable + a: Int + b: String +} + +scalar _Any + +type _Service { + sdl: String +} + +union _Entity = T + +type Query { + _entities(representations: [_Any!]!): [_Entity]! + _service: _Service! +} diff --git a/apollo-federation/tests/subgraph/mod.rs b/apollo-federation/tests/subgraph/mod.rs new file mode 100644 index 0000000000..7610f5925b --- /dev/null +++ b/apollo-federation/tests/subgraph/mod.rs @@ -0,0 +1 @@ +mod parse_expand_tests; diff --git a/apollo-federation/tests/subgraph/parse_expand_tests.rs b/apollo-federation/tests/subgraph/parse_expand_tests.rs new file mode 100644 index 0000000000..6467cbfb0f --- /dev/null +++ b/apollo-federation/tests/subgraph/parse_expand_tests.rs @@ -0,0 +1,164 @@ +use apollo_federation::subgraph::Subgraph; + +#[test] +fn can_parse_and_expand() -> Result<(), String> { + let schema = r#" + extend schema + @link(url: "https://specs.apollo.dev/federation/v2.3", import: [ "@key" ]) + + type Query { + t: T + } + + type T @key(fields: "id") { + id: ID! + x: Int + } + "#; + + let subgraph = Subgraph::parse_and_expand("S1", "http://s1", schema).map_err(|e| { + println!("{}", e); + String::from("failed to parse and expand the subgraph, see errors above for details") + })?; + assert!(subgraph.schema.types.contains_key("T")); + assert!(subgraph.schema.directive_definitions.contains_key("key")); + assert!(subgraph + .schema + .directive_definitions + .contains_key("federation__requires")); + Ok(()) +} + +#[test] +fn can_parse_and_expand_with_renames() -> Result<(), String> { + let schema = r#" + extend schema + @link(url: "https://specs.apollo.dev/federation/v2.3", import: [ { name: "@key", as: "@myKey" }, "@provides" ]) + + type Query { + t: T @provides(fields: "x") + } + + type T @myKey(fields: "id") { + id: ID! + x: Int + } + "#; + + let subgraph = Subgraph::parse_and_expand("S1", "http://s1", schema).map_err(|e| { + println!("{}", e); + String::from("failed to parse and expand the subgraph, see errors above for details") + })?; + assert!(subgraph.schema.directive_definitions.contains_key("myKey")); + assert!(subgraph + .schema + .directive_definitions + .contains_key("provides")); + Ok(()) +} + +#[test] +fn can_parse_and_expand_with_namespace() -> Result<(), String> { + let schema = r#" + extend schema + @link(url: "https://specs.apollo.dev/federation/v2.3", import: [ "@key" ], as: "fed" ) + + type Query { + t: T + } + + type T @key(fields: "id") { + id: ID! + x: Int + } + "#; + + let subgraph = Subgraph::parse_and_expand("S1", "http://s1", schema).map_err(|e| { + println!("{}", e); + String::from("failed to parse and expand the subgraph, see errors above for details") + })?; + assert!(subgraph.schema.directive_definitions.contains_key("key")); + assert!(subgraph + .schema + .directive_definitions + .contains_key("fed__requires")); + Ok(()) +} + +#[test] +fn can_parse_and_expand_preserves_user_definitions() -> Result<(), String> { + let schema = r#" + extend schema + @link(url: "https://specs.apollo.dev/link/v1.0", import: ["Import", "Purpose"]) + @link(url: "https://specs.apollo.dev/federation/v2.3", import: [ "@key" ]) + + type Query { + t: T + } + + type T @key(fields: "id") { + id: ID! + x: Int + } + + enum Purpose { + SECURITY + EXECUTION + } + + scalar Import + + directive @link(url: String, as: String, import: [Import], for: Purpose) repeatable on SCHEMA + "#; + + let subgraph = Subgraph::parse_and_expand("S1", "http://s1", schema).map_err(|e| { + println!("{}", e); + String::from("failed to parse and expand the subgraph, see errors above for details") + })?; + assert!(subgraph.schema.types.contains_key("Purpose")); + Ok(()) +} + +#[test] +fn can_parse_and_expand_works_with_fed_v1() -> Result<(), String> { + let schema = r#" + type Query { + t: T + } + + type T @key(fields: "id") { + id: ID! + x: Int + } + "#; + + let subgraph = Subgraph::parse_and_expand("S1", "http://s1", schema).map_err(|e| { + println!("{}", e); + String::from("failed to parse and expand the subgraph, see errors above for details") + })?; + assert!(subgraph.schema.types.contains_key("T")); + assert!(subgraph.schema.directive_definitions.contains_key("key")); + Ok(()) +} + +#[test] +fn can_parse_and_expand_will_fail_when_importing_same_spec_twice() { + let schema = r#" + extend schema + @link(url: "https://specs.apollo.dev/federation/v2.3", import: [ "@key" ] ) + @link(url: "https://specs.apollo.dev/federation/v2.3", import: [ "@provides" ] ) + + type Query { + t: T + } + + type T @key(fields: "id") { + id: ID! + x: Int + } + "#; + + let result = Subgraph::parse_and_expand("S1", "http://s1", schema) + .expect_err("importing same specification twice should fail"); + assert_eq!("Invalid use of @link in schema: invalid graphql schema - multiple @link imports for the federation specification are not supported", result.to_string()); +} diff --git a/apollo-router-benchmarks/Cargo.toml b/apollo-router-benchmarks/Cargo.toml index 91086440e3..62e409a105 100644 --- a/apollo-router-benchmarks/Cargo.toml +++ b/apollo-router-benchmarks/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "apollo-router-benchmarks" -version = "1.46.0" +version = "1.47.0" authors = ["Apollo Graph, Inc. "] edition = "2021" license = "Elastic-2.0" diff --git a/apollo-router-scaffold/Cargo.toml b/apollo-router-scaffold/Cargo.toml index 52b903aea1..353837020b 100644 --- a/apollo-router-scaffold/Cargo.toml +++ b/apollo-router-scaffold/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "apollo-router-scaffold" -version = "1.46.0" +version = "1.47.0" authors = ["Apollo Graph, Inc. "] edition = "2021" license = "Elastic-2.0" diff --git a/apollo-router-scaffold/src/lib.rs b/apollo-router-scaffold/src/lib.rs index 4fe71599b6..1731e67172 100644 --- a/apollo-router-scaffold/src/lib.rs +++ b/apollo-router-scaffold/src/lib.rs @@ -45,6 +45,7 @@ mod test { // this test takes a while, I hope the above test name // let users know they should not worry and wait a bit. // Hang in there! + // Note that we configure nextest to use all threads for this test as invoking rustc will use all available CPU and cause timing tests to fail. fn test_scaffold() { let manifest_dir = PathBuf::from(std::env::var_os("CARGO_MANIFEST_DIR").unwrap()); let repo_root = manifest_dir.parent().unwrap(); diff --git a/apollo-router-scaffold/templates/base/Cargo.toml b/apollo-router-scaffold/templates/base/Cargo.toml index f91e353964..521a9198d7 100644 --- a/apollo-router-scaffold/templates/base/Cargo.toml +++ b/apollo-router-scaffold/templates/base/Cargo.toml @@ -22,7 +22,7 @@ apollo-router = { path ="{{integration_test}}apollo-router" } apollo-router = { git="https://github.com/apollographql/router.git", branch="{{branch}}" } {{else}} # Note if you update these dependencies then also update xtask/Cargo.toml -apollo-router = "1.46.0" +apollo-router = "1.47.0" {{/if}} {{/if}} async-trait = "0.1.52" diff --git a/apollo-router-scaffold/templates/base/xtask/Cargo.toml b/apollo-router-scaffold/templates/base/xtask/Cargo.toml index 4b855db8a5..39354e9139 100644 --- a/apollo-router-scaffold/templates/base/xtask/Cargo.toml +++ b/apollo-router-scaffold/templates/base/xtask/Cargo.toml @@ -13,7 +13,7 @@ apollo-router-scaffold = { path ="{{integration_test}}apollo-router-scaffold" } {{#if branch}} apollo-router-scaffold = { git="https://github.com/apollographql/router.git", branch="{{branch}}" } {{else}} -apollo-router-scaffold = { git = "https://github.com/apollographql/router.git", tag = "v1.46.0" } +apollo-router-scaffold = { git = "https://github.com/apollographql/router.git", tag = "v1.47.0" } {{/if}} {{/if}} anyhow = "1.0.58" diff --git a/apollo-router/Cargo.toml b/apollo-router/Cargo.toml index 6f9a73fcdc..9e9df5d405 100644 --- a/apollo-router/Cargo.toml +++ b/apollo-router/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "apollo-router" -version = "1.46.0" +version = "1.47.0" authors = ["Apollo Graph, Inc. "] repository = "https://github.com/apollographql/router/" documentation = "https://docs.rs/apollo-router" @@ -57,6 +57,9 @@ docs_rs = ["router-bridge/docs_rs"] # and not yet ready for production use. telemetry_next = [] +# is set when ci builds take place. It allows us to disable some tests when CI is running on certain platforms. +ci = [] + [package.metadata.docs.rs] features = ["docs_rs"] @@ -65,7 +68,7 @@ askama = "0.12.1" access-json = "0.1.0" anyhow = "1.0.80" apollo-compiler.workspace = true -apollo-federation = "=0.0.11" +apollo-federation = { path = "../apollo-federation", version = "=1.47.0-rc.0" } arc-swap = "1.6.0" async-channel = "1.9.0" async-compression = { version = "0.4.6", features = [ @@ -103,7 +106,7 @@ flate2 = "1.0.28" fred = { version = "7.1.2", features = ["enable-rustls"] } futures = { version = "0.3.30", features = ["thread-pool"] } graphql_client = "0.13.0" -hex = { version = "0.4.3", features = ["serde"] } +hex.workspace = true http.workspace = true http-body = "0.4.6" heck = "0.4.1" @@ -146,7 +149,9 @@ once_cell = "1.19.0" # https://github.com/apollographql/router/pull/1509. A comment which exists # there (and on `tracing` packages below) should be updated should this change. opentelemetry = { version = "0.20.0", features = ["trace", "metrics"] } -opentelemetry_sdk = { version = "0.20.0", default-features = false, features = ["trace"] } +opentelemetry_sdk = { version = "0.20.0", default-features = false, features = [ + "trace", +] } opentelemetry_api = "0.20.0" opentelemetry-aws = "0.8.0" opentelemetry-datadog = { version = "0.8.0", features = ["reqwest-client"] } @@ -254,7 +259,7 @@ aws-credential-types = "1.1.6" aws-config = "1.1.6" aws-types = "1.1.6" aws-smithy-runtime-api = { version = "1.1.6", features = ["client"] } -sha1 = "0.10.6" +sha1.workspace = true tracing-serde = "0.1.3" time = { version = "0.3.34", features = ["serde"] } similar = { version = "2.4.0", features = ["inline"] } @@ -283,7 +288,7 @@ axum = { version = "0.6.20", features = [ ecdsa = { version = "0.16.9", features = ["signing", "pem", "pkcs8"] } fred = { version = "7.1.2", features = ["enable-rustls", "mocks"] } futures-test = "0.3.30" -insta = { version = "1.35.1", features = ["json", "redactions", "yaml"] } +insta.workspace = true maplit = "1.0.2" memchr = { version = "2.7.1", default-features = false } mockall = "0.11.4" @@ -311,7 +316,7 @@ rhai = { version = "1.17.1", features = [ "testing-environ", ] } serial_test = { version = "3.0.0" } -tempfile = "3.10.0" +tempfile.workspace = true test-log = { version = "0.2.14", default-features = false, features = [ "trace", ] } diff --git a/apollo-router/src/axum_factory/axum_http_server_factory.rs b/apollo-router/src/axum_factory/axum_http_server_factory.rs index 6424a20448..2747a81ca4 100644 --- a/apollo-router/src/axum_factory/axum_http_server_factory.rs +++ b/apollo-router/src/axum_factory/axum_http_server_factory.rs @@ -1,4 +1,5 @@ //! Axum http server factory. Axum provides routing capability on top of Hyper HTTP. +use std::fmt::Display; use std::pin::Pin; use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicU64; @@ -27,6 +28,7 @@ use hyper::Body; use itertools::Itertools; use multimap::MultiMap; use serde::Serialize; +use serde_json::json; #[cfg(unix)] use tokio::net::UnixListener; use tokio::sync::mpsc; @@ -52,6 +54,7 @@ use crate::axum_factory::listeners::get_extra_listeners; use crate::axum_factory::listeners::serve_router_on_listen_addr; use crate::configuration::Configuration; use crate::configuration::ListenAddr; +use crate::graphql; use crate::http_server_factory::HttpServerFactory; use crate::http_server_factory::HttpServerHandle; use crate::http_server_factory::Listener; @@ -644,10 +647,7 @@ async fn handle_graphql( .in_current_span(); let res = match tokio::task::spawn(task).await { Ok(res) => res, - Err(err) => { - let msg = format!("router service call failed: {err}"); - return (StatusCode::INTERNAL_SERVER_ERROR, msg).into_response(); - } + Err(err) => return internal_server_error(err), }; cancel_handler.on_response(); res @@ -679,8 +679,7 @@ async fn handle_graphql( return Elapsed::new().into_response(); } - let msg = format!("router service call failed: {err}"); - (StatusCode::INTERNAL_SERVER_ERROR, msg).into_response() + internal_server_error(err) } Ok(response) => { let (mut parts, body) = response.response.into_parts(); @@ -705,6 +704,27 @@ async fn handle_graphql( } } +fn internal_server_error(err: T) -> Response +where + T: Display, +{ + tracing::error!( + code = "INTERNAL_SERVER_ERROR", + %err, + ); + + // This intentionally doesn't include an error message as this could represent leakage of internal information. + // The error message is logged above. + let error = graphql::Error::builder() + .message("internal server error") + .extension_code("INTERNAL_SERVER_ERROR") + .build(); + + let response = graphql::Response::builder().error(error).build(); + + (StatusCode::INTERNAL_SERVER_ERROR, Json(json!(response))).into_response() +} + struct CancelHandler<'a> { context: &'a Context, got_first_response: bool, diff --git a/apollo-router/src/batching.rs b/apollo-router/src/batching.rs index 302c7d72d9..dd8a1a19cf 100644 --- a/apollo-router/src/batching.rs +++ b/apollo-router/src/batching.rs @@ -425,7 +425,7 @@ pub(crate) async fn assemble_batch( ) -> Result< ( String, - Context, + Vec, http::Request, Vec>>, ), @@ -441,12 +441,12 @@ pub(crate) async fn assemble_batch( // Construct the actual byte body of the batched request let bytes = hyper::body::to_bytes(serde_json::to_string(&gql_requests)?).await?; + // Retain the various contexts for later use + let contexts = requests + .iter() + .map(|x| x.context.clone()) + .collect::>(); // Grab the common info from the first request - let context = requests - .first() - .ok_or(SubgraphBatchingError::RequestsIsEmpty)? - .context - .clone(); let first_request = requests .into_iter() .next() @@ -461,7 +461,7 @@ pub(crate) async fn assemble_batch( // Generate the final request and pass it up let request = http::Request::from_parts(parts, Body::from(bytes)); - Ok((operation_name, context, request, txs)) + Ok((operation_name, contexts, request, txs)) } #[cfg(test)] @@ -511,11 +511,23 @@ mod tests { }) .unzip(); + // Create a vector of the input request context IDs for comparison + let input_context_ids = requests + .iter() + .map(|r| r.request.context.id.clone()) + .collect::>(); // Assemble them - let (op_name, _context, request, txs) = assemble_batch(requests) + let (op_name, contexts, request, txs) = assemble_batch(requests) .await .expect("it can assemble a batch"); + let output_context_ids = contexts + .iter() + .map(|r| r.id.clone()) + .collect::>(); + // Make sure all of our contexts are preserved during assembly + assert_eq!(input_context_ids, output_context_ids); + // Make sure that the name of the entire batch is that of the first assert_eq!(op_name, "batch_test_0"); diff --git a/apollo-router/src/configuration/metrics.rs b/apollo-router/src/configuration/metrics.rs index 036e5d1536..ca64ba144d 100644 --- a/apollo-router/src/configuration/metrics.rs +++ b/apollo-router/src/configuration/metrics.rs @@ -53,6 +53,22 @@ impl Metrics { } impl InstrumentData { + fn get_first_key_from_path( + attributes: &mut HashMap, + attr_name: &str, + path: &str, + value: &Value, + ) { + if let Ok(json_path) = JsonPathInst::from_str(path) { + let value_at_path = json_path.find_slice(value).into_iter().next(); + if let Some(Value::Object(children)) = value_at_path.as_deref() { + if let Some(first_key) = children.keys().next() { + attributes.insert(attr_name.to_string(), first_key.clone().into()); + } + } + } + } + fn get_value_from_path( attributes: &mut HashMap, attr_name: &str, @@ -355,6 +371,27 @@ impl InstrumentData { opt.limits.max_files, "$.limits.max_files" ); + + populate_config_instrument!( + apollo.router.config.experimental_demand_control, + "$.experimental_demand_control[?(@.enabled == true)]", + opt.mode, + "$.mode" + ); + + // We need to update the entry we just made because the selected strategy is a named object in the config. + // The jsonpath spec doesn't include a utility for getting the keys out of an object, so we do it manually. + if let Some((_, demand_control_attributes)) = self + .data + .get_mut(&"apollo.router.config.experimental_demand_control".to_string()) + { + Self::get_first_key_from_path( + demand_control_attributes, + "opt.strategy", + "$.experimental_demand_control[?(@.enabled == true)].strategy", + yaml, + ); + } } fn populate_env_instrument(&mut self) { diff --git a/apollo-router/src/configuration/mod.rs b/apollo-router/src/configuration/mod.rs index e3839b45bb..cc9017e3cb 100644 --- a/apollo-router/src/configuration/mod.rs +++ b/apollo-router/src/configuration/mod.rs @@ -414,6 +414,27 @@ impl Configuration { .build() ).build()) } + + pub(crate) fn js_query_planner_config(&self) -> router_bridge::planner::QueryPlannerConfig { + router_bridge::planner::QueryPlannerConfig { + reuse_query_fragments: self.supergraph.reuse_query_fragments, + generate_query_fragments: Some(self.supergraph.generate_query_fragments), + incremental_delivery: Some(router_bridge::planner::IncrementalDeliverySupport { + enable_defer: Some(self.supergraph.defer_support), + }), + graphql_validation: false, + debug: Some(router_bridge::planner::QueryPlannerDebugConfig { + bypass_planner_for_single_subgraph: None, + max_evaluated_plans: self + .supergraph + .query_planning + .experimental_plans_limit + .or(Some(10000)), + paths_limit: self.supergraph.query_planning.experimental_paths_limit, + }), + type_conditioned_fetching: self.experimental_type_conditioned_fetching, + } + } } impl Default for Configuration { diff --git a/apollo-router/src/configuration/schema.rs b/apollo-router/src/configuration/schema.rs index 4223c835de..a78015ab63 100644 --- a/apollo-router/src/configuration/schema.rs +++ b/apollo-router/src/configuration/schema.rs @@ -3,6 +3,7 @@ use std::borrow::Cow; use std::cmp::Ordering; use std::fmt::Write; +use std::mem; use std::sync::OnceLock; use itertools::Itertools; @@ -10,7 +11,12 @@ use jsonschema::error::ValidationErrorKind; use jsonschema::Draft; use jsonschema::JSONSchema; use schemars::gen::SchemaSettings; +use schemars::schema::Metadata; use schemars::schema::RootSchema; +use schemars::schema::SchemaObject; +use schemars::visit::visit_root_schema; +use schemars::visit::visit_schema_object; +use schemars::visit::Visitor; use yaml_rust::scanner::Marker; use super::expansion::coerce; @@ -25,12 +31,39 @@ pub(crate) use crate::configuration::upgrade::upgrade_configuration; const NUMBER_OF_PREVIOUS_LINES_TO_DISPLAY: usize = 5; +/// This needs to exist because Schemars incorrectly generates references with spaces in them. +/// We just rename them. +#[derive(Debug, Clone)] +struct RefRenameVisitor; + +impl Visitor for RefRenameVisitor { + fn visit_root_schema(&mut self, root: &mut RootSchema) { + visit_root_schema(self, root); + root.definitions = mem::take(&mut root.definitions) + .into_iter() + .map(|(k, v)| (k.replace(' ', "_"), v)) + .collect(); + } + fn visit_schema_object(&mut self, schema: &mut SchemaObject) { + if let Some(reference) = &mut schema.reference { + schema.metadata = Some(Box::new(Metadata { + description: Some(reference.clone()), + ..Default::default() + })); + *reference = reference.replace(' ', "_"); + } + + visit_schema_object(self, schema); + } +} + /// Generate a JSON schema for the configuration. pub(crate) fn generate_config_schema() -> RootSchema { let settings = SchemaSettings::draft07().with(|s| { s.option_nullable = true; s.option_add_null_type = false; - s.inline_subschemas = true; + s.inline_subschemas = false; + s.visitors = vec![Box::new(RefRenameVisitor)] }); // Manually patch up the schema @@ -89,10 +122,15 @@ pub(crate) fn validate_yaml_configuration( let config_schema = serde_json::to_value(generate_config_schema()) .expect("failed to parse configuration schema"); - JSONSchema::options() + let result = JSONSchema::options() .with_draft(Draft::Draft7) - .compile(&config_schema) - .expect("failed to compile configuration schema") + .compile(&config_schema); + match result { + Ok(schema) => schema, + Err(e) => { + panic!("failed to compile configuration schema: {}", e) + } + } }); if migration == Mode::Upgrade { diff --git a/apollo-router/src/configuration/snapshots/apollo_router__configuration__metrics__test__metrics@demand_control.router.yaml.snap b/apollo-router/src/configuration/snapshots/apollo_router__configuration__metrics__test__metrics@demand_control.router.yaml.snap new file mode 100644 index 0000000000..0f2821a59a --- /dev/null +++ b/apollo-router/src/configuration/snapshots/apollo_router__configuration__metrics__test__metrics@demand_control.router.yaml.snap @@ -0,0 +1,11 @@ +--- +source: apollo-router/src/configuration/metrics.rs +expression: "&metrics.non_zero()" +--- +- name: apollo.router.config.experimental_demand_control + data: + datapoints: + - value: 1 + attributes: + opt.mode: measure + opt.strategy: static_estimated diff --git a/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap b/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap index 2917751d0d..d7e2bb53e1 100644 --- a/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap +++ b/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap @@ -3,96199 +3,7279 @@ source: apollo-router/src/configuration/tests.rs expression: "&schema" --- { - "$schema": "https://json-schema.org/draft/2019-09/schema", - "title": "Configuration", - "description": "The configuration for the router.\n\nCan be created through `serde::Deserialize` from various formats, or inline in Rust code with `serde_json::json!` and `serde_json::from_value`.", - "type": "object", - "properties": { - "apq": { - "description": "Configures automatic persisted queries", - "default": { - "enabled": true, - "router": { - "cache": { - "in_memory": { - "limit": 512 - }, - "redis": null - } - }, - "subgraph": { - "all": { - "enabled": false - }, - "subgraphs": {} - } - }, - "type": "object", - "properties": { - "enabled": { - "description": "Activates Automatic Persisted Queries (enabled by default)", - "default": true, - "type": "boolean" - }, - "router": { - "description": "Router level (APQ) configuration", - "default": { - "cache": { - "in_memory": { - "limit": 512 - }, - "redis": null - } - }, - "type": "object", + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "definitions": { + "AWSSigV4Config": { + "description": "Configure AWS sigv4 auth.", + "oneOf": [ + { + "additionalProperties": false, "properties": { - "cache": { - "description": "Cache configuration", - "default": { - "in_memory": { - "limit": 512 - }, - "redis": null - }, - "type": "object", - "properties": { - "in_memory": { - "description": "Configures the in memory cache (always active)", - "default": { - "limit": 512 - }, - "type": "object", - "required": [ - "limit" - ], - "properties": { - "limit": { - "description": "Number of entries in the Least Recently Used cache", - "type": "integer", - "format": "uint", - "minimum": 1.0 - } - }, - "additionalProperties": false - }, - "redis": { - "description": "Configures and activates the Redis cache", - "type": "object", - "required": [ - "urls" - ], - "properties": { - "namespace": { - "description": "namespace used to prefix Redis keys", - "type": "string", - "nullable": true - }, - "password": { - "description": "Redis password if not provided in the URLs. This field takes precedence over the password in the URL", - "type": "string", - "nullable": true - }, - "required_to_start": { - "description": "Prevents the router from starting if it cannot connect to Redis", - "default": false, - "type": "boolean" - }, - "reset_ttl": { - "description": "When a TTL is set on a key, reset it when reading the data from that key", - "default": true, - "type": "boolean" - }, - "timeout": { - "description": "Redis request timeout (default: 2ms)", - "type": "string", - "nullable": true - }, - "tls": { - "description": "TLS client configuration", - "type": "object", - "properties": { - "certificate_authorities": { - "description": "list of certificate authorities in PEM format", - "type": "string", - "nullable": true - }, - "client_authentication": { - "description": "client certificate authentication", - "type": "object", - "required": [ - "certificate_chain", - "key" - ], - "properties": { - "certificate_chain": { - "description": "list of certificates in PEM format", - "writeOnly": true, - "type": "string" - }, - "key": { - "description": "key in PEM format", - "writeOnly": true, - "type": "string" - } - }, - "additionalProperties": false, - "nullable": true - } - }, - "additionalProperties": false, - "nullable": true - }, - "ttl": { - "description": "TTL for entries", - "type": "string", - "nullable": true - }, - "urls": { - "description": "List of URLs to the Redis cluster", - "type": "array", - "items": { - "type": "string", - "format": "uri" - } - }, - "username": { - "description": "Redis username if not provided in the URLs. This field takes precedence over the username in the URL", - "type": "string", - "nullable": true - } - }, - "additionalProperties": false, - "nullable": true - } - }, - "additionalProperties": false + "hardcoded": { + "$ref": "#/definitions/AWSSigV4HardcodedConfig", + "description": "#/definitions/AWSSigV4HardcodedConfig" } }, - "additionalProperties": false + "required": [ + "hardcoded" + ], + "type": "object" }, - "subgraph": { - "description": "Configuration options pertaining to the subgraph server component.", - "default": { - "all": { - "enabled": false - }, - "subgraphs": {} - }, - "type": "object", + { + "additionalProperties": false, "properties": { - "all": { - "description": "options applying to all subgraphs", - "default": { - "enabled": false - }, - "type": "object", - "properties": { - "enabled": { - "description": "Enable", - "default": false, - "type": "boolean" - } - }, - "additionalProperties": false - }, - "subgraphs": { - "description": "per subgraph options", - "default": {}, - "type": "object", - "additionalProperties": { - "description": "Subgraph level Automatic Persisted Queries (APQ) configuration", - "type": "object", - "properties": { - "enabled": { - "description": "Enable", - "default": false, - "type": "boolean" - } - }, - "additionalProperties": false - } + "default_chain": { + "$ref": "#/definitions/DefaultChainConfig", + "description": "#/definitions/DefaultChainConfig" } - } + }, + "required": [ + "default_chain" + ], + "type": "object" } - }, - "additionalProperties": false + ] }, - "authentication": { - "description": "Authentication", - "type": "object", + "AWSSigV4HardcodedConfig": { + "additionalProperties": false, + "description": "Hardcoded Config using access_key and secret. Prefer using DefaultChain instead.", "properties": { - "router": { - "description": "Router configuration", - "type": "object", - "required": [ - "jwt" - ], - "properties": { - "jwt": { - "description": "The JWT configuration", - "type": "object", - "required": [ - "jwks" - ], - "properties": { - "header_name": { - "description": "HTTP header expected to contain JWT", - "default": "authorization", - "type": "string" - }, - "header_value_prefix": { - "description": "Header value prefix", - "default": "Bearer", - "type": "string" - }, - "ignore_other_prefixes": { - "description": "Whether to ignore any mismatched prefixes", - "default": false, - "type": "boolean" - }, - "jwks": { - "description": "List of JWKS used to verify tokens", - "type": "array", - "items": { - "type": "object", - "required": [ - "url" - ], - "properties": { - "algorithms": { - "description": "List of accepted algorithms. Possible values are `HS256`, `HS384`, `HS512`, `ES256`, `ES384`, `RS256`, `RS384`, `RS512`, `PS256`, `PS384`, `PS512`, `EdDSA`", - "type": "array", - "items": { - "type": "string" - }, - "nullable": true - }, - "headers": { - "description": "List of headers to add to the JWKS request", - "type": "array", - "items": { - "description": "Insert a header", - "type": "object", - "required": [ - "name", - "value" - ], - "properties": { - "name": { - "description": "The name of the header", - "type": "string" - }, - "value": { - "description": "The value for the header", - "type": "string" - } - }, - "additionalProperties": false - } - }, - "issuer": { - "description": "Expected issuer for tokens verified by that JWKS", - "type": "string", - "nullable": true - }, - "poll_interval": { - "description": "Polling interval for each JWKS endpoint in human-readable format; defaults to 60s", - "default": { - "secs": 60, - "nanos": 0 - }, - "type": "string" - }, - "url": { - "description": "Retrieve the JWK Set", - "type": "string" - } - }, - "additionalProperties": false - } - }, - "sources": { - "description": "Alternative sources to extract the JWT", - "type": "array", - "items": { - "oneOf": [ - { - "type": "object", - "required": [ - "type" - ], - "properties": { - "name": { - "description": "HTTP header expected to contain JWT", - "default": "authorization", - "type": "string" - }, - "type": { - "type": "string", - "enum": [ - "header" - ] - }, - "value_prefix": { - "description": "Header value prefix", - "default": "Bearer", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "name", - "type" - ], - "properties": { - "name": { - "description": "Name of the cookie containing the JWT", - "type": "string" - }, - "type": { - "type": "string", - "enum": [ - "cookie" - ] - } - }, - "additionalProperties": false - } - ] - } - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false, - "nullable": true + "access_key_id": { + "description": "The ID for this access key.", + "type": "string" }, - "subgraph": { - "description": "Subgraph configuration", - "type": "object", - "properties": { - "all": { - "description": "Configuration that will apply to all subgraphs.", - "oneOf": [ - { - "type": "object", - "required": [ - "aws_sig_v4" - ], - "properties": { - "aws_sig_v4": { - "description": "Configure AWS sigv4 auth.", - "oneOf": [ - { - "type": "object", - "required": [ - "hardcoded" - ], - "properties": { - "hardcoded": { - "description": "Hardcoded Config using access_key and secret. Prefer using DefaultChain instead.", - "type": "object", - "required": [ - "access_key_id", - "region", - "secret_access_key", - "service_name" - ], - "properties": { - "access_key_id": { - "description": "The ID for this access key.", - "type": "string" - }, - "assume_role": { - "description": "Specify assumed role configuration.", - "type": "object", - "required": [ - "role_arn", - "session_name" - ], - "properties": { - "external_id": { - "description": "Unique identifier that might be required when you assume a role in another account.", - "type": "string", - "nullable": true - }, - "role_arn": { - "description": "Amazon Resource Name (ARN) for the role assumed when making requests", - "type": "string" - }, - "session_name": { - "description": "Uniquely identify a session when the same role is assumed by different principals or for different reasons.", - "type": "string" - } - }, - "additionalProperties": false, - "nullable": true - }, - "region": { - "description": "The AWS region this chain applies to.", - "type": "string" - }, - "secret_access_key": { - "description": "The secret key used to sign requests.", - "type": "string" - }, - "service_name": { - "description": "The service you're trying to access, eg: \"s3\", \"vpc-lattice-svcs\", etc.", - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "default_chain" - ], - "properties": { - "default_chain": { - "description": "Configuration of the DefaultChainProvider", - "type": "object", - "required": [ - "region", - "service_name" - ], - "properties": { - "assume_role": { - "description": "Specify assumed role configuration.", - "type": "object", - "required": [ - "role_arn", - "session_name" - ], - "properties": { - "external_id": { - "description": "Unique identifier that might be required when you assume a role in another account.", - "type": "string", - "nullable": true - }, - "role_arn": { - "description": "Amazon Resource Name (ARN) for the role assumed when making requests", - "type": "string" - }, - "session_name": { - "description": "Uniquely identify a session when the same role is assumed by different principals or for different reasons.", - "type": "string" - } - }, - "additionalProperties": false, - "nullable": true - }, - "profile_name": { - "description": "The profile name used by this provider", - "type": "string", - "nullable": true - }, - "region": { - "description": "The AWS region this chain applies to.", - "type": "string" - }, - "service_name": { - "description": "The service you're trying to access, eg: \"s3\", \"vpc-lattice-svcs\", etc.", - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - } - ], - "nullable": true - }, - "subgraphs": { - "description": "Create a configuration that will apply only to a specific subgraph.", - "type": "object", - "additionalProperties": { - "oneOf": [ - { - "type": "object", - "required": [ - "aws_sig_v4" - ], - "properties": { - "aws_sig_v4": { - "description": "Configure AWS sigv4 auth.", - "oneOf": [ - { - "type": "object", - "required": [ - "hardcoded" - ], - "properties": { - "hardcoded": { - "description": "Hardcoded Config using access_key and secret. Prefer using DefaultChain instead.", - "type": "object", - "required": [ - "access_key_id", - "region", - "secret_access_key", - "service_name" - ], - "properties": { - "access_key_id": { - "description": "The ID for this access key.", - "type": "string" - }, - "assume_role": { - "description": "Specify assumed role configuration.", - "type": "object", - "required": [ - "role_arn", - "session_name" - ], - "properties": { - "external_id": { - "description": "Unique identifier that might be required when you assume a role in another account.", - "type": "string", - "nullable": true - }, - "role_arn": { - "description": "Amazon Resource Name (ARN) for the role assumed when making requests", - "type": "string" - }, - "session_name": { - "description": "Uniquely identify a session when the same role is assumed by different principals or for different reasons.", - "type": "string" - } - }, - "additionalProperties": false, - "nullable": true - }, - "region": { - "description": "The AWS region this chain applies to.", - "type": "string" - }, - "secret_access_key": { - "description": "The secret key used to sign requests.", - "type": "string" - }, - "service_name": { - "description": "The service you're trying to access, eg: \"s3\", \"vpc-lattice-svcs\", etc.", - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "default_chain" - ], - "properties": { - "default_chain": { - "description": "Configuration of the DefaultChainProvider", - "type": "object", - "required": [ - "region", - "service_name" - ], - "properties": { - "assume_role": { - "description": "Specify assumed role configuration.", - "type": "object", - "required": [ - "role_arn", - "session_name" - ], - "properties": { - "external_id": { - "description": "Unique identifier that might be required when you assume a role in another account.", - "type": "string", - "nullable": true - }, - "role_arn": { - "description": "Amazon Resource Name (ARN) for the role assumed when making requests", - "type": "string" - }, - "session_name": { - "description": "Uniquely identify a session when the same role is assumed by different principals or for different reasons.", - "type": "string" - } - }, - "additionalProperties": false, - "nullable": true - }, - "profile_name": { - "description": "The profile name used by this provider", - "type": "string", - "nullable": true - }, - "region": { - "description": "The AWS region this chain applies to.", - "type": "string" - }, - "service_name": { - "description": "The service you're trying to access, eg: \"s3\", \"vpc-lattice-svcs\", etc.", - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - } - ] - } - } - }, - "additionalProperties": false, + "assume_role": { + "$ref": "#/definitions/AssumeRoleProvider", + "description": "#/definitions/AssumeRoleProvider", "nullable": true + }, + "region": { + "description": "The AWS region this chain applies to.", + "type": "string" + }, + "secret_access_key": { + "description": "The secret key used to sign requests.", + "type": "string" + }, + "service_name": { + "description": "The service you're trying to access, eg: \"s3\", \"vpc-lattice-svcs\", etc.", + "type": "string" } }, - "additionalProperties": false + "required": [ + "access_key_id", + "region", + "secret_access_key", + "service_name" + ], + "type": "object" }, - "authorization": { - "description": "Authorization plugin", - "type": "object", + "ActiveRequestsAttributes": { + "additionalProperties": false, "properties": { - "directives": { - "description": "`@authenticated`, `@requiresScopes` and `@policy` directives", - "type": "object", - "properties": { - "dry_run": { - "description": "generates the authorization error messages without modying the query", - "default": false, - "type": "boolean" - }, - "enabled": { - "description": "enables the `@authenticated` and `@requiresScopes` directives", - "default": true, - "type": "boolean" - }, - "errors": { - "description": "authorization errors behaviour", - "default": { - "log": true, - "response": "errors" - }, - "type": "object", - "properties": { - "log": { - "description": "log authorization errors", - "default": true, - "type": "boolean" - }, - "response": { - "description": "location of authorization errors in the GraphQL response", - "default": "errors", - "oneOf": [ - { - "description": "store authorization errors in the response errors", - "type": "string", - "enum": [ - "errors" - ] - }, - { - "description": "store authorization errors in the response extensions", - "type": "string", - "enum": [ - "extensions" - ] - }, - { - "description": "do not add the authorization errors to the GraphQL response", - "type": "string", - "enum": [ - "disabled" - ] - } - ] - } - } - }, - "reject_unauthorized": { - "description": "refuse a query entirely if any part would be filtered", - "default": false, - "type": "boolean" - } - } + "http.request.method": { + "default": false, + "description": "The HTTP request method", + "type": "boolean" }, - "require_authentication": { - "description": "Reject unauthenticated requests", + "server.address": { + "default": false, + "description": "The server address", + "type": "boolean" + }, + "server.port": { "default": false, + "description": "The server port", + "type": "boolean" + }, + "url.scheme": { + "default": false, + "description": "The URL scheme", "type": "boolean" } - } + }, + "type": "object" }, - "batching": { - "description": "Batching configuration.", - "default": { - "enabled": false, - "mode": "batch_http_link", - "subgraph": null + "AgentConfig": { + "additionalProperties": false, + "properties": { + "endpoint": { + "$ref": "#/definitions/SocketEndpoint", + "description": "#/definitions/SocketEndpoint" + } }, - "type": "object", - "required": [ - "mode" - ], + "type": "object" + }, + "ApiSchemaMode": { + "description": "API schema generation modes.", + "oneOf": [ + { + "description": "Use the new Rust-based implementation.", + "enum": [ + "new" + ], + "type": "string" + }, + { + "description": "Use the old JavaScript-based implementation.", + "enum": [ + "legacy" + ], + "type": "string" + }, + { + "description": "Use Rust-based and Javascript-based implementations side by side, logging warnings if the implementations disagree.", + "enum": [ + "both" + ], + "type": "string" + } + ] + }, + "ApolloMetricsGenerationMode": { + "description": "Apollo usage report signature and referenced field generation modes.", + "oneOf": [ + { + "description": "Use the new Rust-based implementation.", + "enum": [ + "new" + ], + "type": "string" + }, + { + "description": "Use the old JavaScript-based implementation.", + "enum": [ + "legacy" + ], + "type": "string" + }, + { + "description": "Use Rust-based and Javascript-based implementations side by side, logging warnings if the implementations disagree.", + "enum": [ + "both" + ], + "type": "string" + } + ] + }, + "Apq": { + "additionalProperties": false, + "description": "Automatic Persisted Queries (APQ) configuration", "properties": { "enabled": { - "description": "Activates Batching (disabled by default)", - "default": false, + "default": true, + "description": "Activates Automatic Persisted Queries (enabled by default)", "type": "boolean" }, - "mode": { - "description": "Batching mode", - "oneOf": [ - { - "description": "batch_http_link", - "type": "string", - "enum": [ - "batch_http_link" - ] - } - ] + "router": { + "$ref": "#/definitions/Router", + "description": "#/definitions/Router" }, "subgraph": { - "description": "Subgraph options for batching", - "type": "object", - "properties": { - "all": { - "description": "options applying to all subgraphs", - "default": { - "enabled": false - }, - "type": "object", - "required": [ - "enabled" - ], - "properties": { - "enabled": { - "description": "Whether this batching config should be enabled", - "type": "boolean" - } - } - }, - "subgraphs": { - "description": "per subgraph options", - "default": {}, - "type": "object", - "additionalProperties": { - "description": "Common options for configuring subgraph batching", - "type": "object", - "required": [ - "enabled" - ], - "properties": { - "enabled": { - "description": "Whether this batching config should be enabled", - "type": "boolean" - } - } - } - } - }, - "nullable": true + "$ref": "#/definitions/SubgraphConfiguration_for_SubgraphApq", + "description": "#/definitions/SubgraphConfiguration_for_SubgraphApq" } }, - "additionalProperties": false + "type": "object" }, - "coprocessor": { - "description": "Configures the externalization plugin", - "type": "object", + "AssumeRoleProvider": { + "additionalProperties": false, + "description": "Specify assumed role configuration.", + "properties": { + "external_id": { + "description": "Unique identifier that might be required when you assume a role in another account.", + "nullable": true, + "type": "string" + }, + "role_arn": { + "description": "Amazon Resource Name (ARN) for the role assumed when making requests", + "type": "string" + }, + "session_name": { + "description": "Uniquely identify a session when the same role is assumed by different principals or for different reasons.", + "type": "string" + } + }, "required": [ - "url" + "role_arn", + "session_name" ], - "properties": { - "execution": { - "description": "The execution stage request/response configuration", - "default": { - "request": { - "headers": false, - "context": false, - "body": false, - "sdl": false, - "method": false, - "query_plan": false - }, - "response": { - "headers": false, - "context": false, - "body": false, - "sdl": false, - "status_code": false - } + "type": "object" + }, + "AttributeArray": { + "anyOf": [ + { + "description": "Array of bools", + "items": { + "type": "boolean" }, - "type": "object", - "properties": { - "request": { - "description": "The request configuration", - "default": { - "headers": false, - "context": false, - "body": false, - "sdl": false, - "method": false, - "query_plan": false - }, - "type": "object", - "properties": { - "body": { - "description": "Send the body", - "default": false, - "type": "boolean" - }, - "context": { - "description": "Send the context", - "default": false, - "type": "boolean" - }, - "headers": { - "description": "Send the headers", - "default": false, - "type": "boolean" - }, - "method": { - "description": "Send the method", - "default": false, - "type": "boolean" - }, - "query_plan": { - "description": "Send the query plan", - "default": false, - "type": "boolean" - }, - "sdl": { - "description": "Send the SDL", - "default": false, - "type": "boolean" - } - }, - "additionalProperties": false - }, - "response": { - "description": "What information is passed to a router request/response stage", - "default": { - "headers": false, - "context": false, - "body": false, - "sdl": false, - "status_code": false - }, - "type": "object", - "properties": { - "body": { - "description": "Send the body", - "default": false, - "type": "boolean" - }, - "context": { - "description": "Send the context", - "default": false, - "type": "boolean" - }, - "headers": { - "description": "Send the headers", - "default": false, - "type": "boolean" - }, - "sdl": { - "description": "Send the SDL", - "default": false, - "type": "boolean" - }, - "status_code": { - "description": "Send the HTTP status", - "default": false, - "type": "boolean" - } - }, - "additionalProperties": false - } - } - }, - "router": { - "description": "The router stage request/response configuration", - "default": { - "request": { - "headers": false, - "context": false, - "body": false, - "sdl": false, - "path": false, - "method": false - }, - "response": { - "headers": false, - "context": false, - "body": false, - "sdl": false, - "status_code": false - } - }, - "type": "object", - "properties": { - "request": { - "description": "The request configuration", - "default": { - "headers": false, - "context": false, - "body": false, - "sdl": false, - "path": false, - "method": false - }, - "type": "object", - "properties": { - "body": { - "description": "Send the body", - "default": false, - "type": "boolean" - }, - "context": { - "description": "Send the context", - "default": false, - "type": "boolean" - }, - "headers": { - "description": "Send the headers", - "default": false, - "type": "boolean" - }, - "method": { - "description": "Send the method", - "default": false, - "type": "boolean" - }, - "path": { - "description": "Send the path", - "default": false, - "type": "boolean" - }, - "sdl": { - "description": "Send the SDL", - "default": false, - "type": "boolean" - } - }, - "additionalProperties": false - }, - "response": { - "description": "The response configuration", - "default": { - "headers": false, - "context": false, - "body": false, - "sdl": false, - "status_code": false - }, - "type": "object", - "properties": { - "body": { - "description": "Send the body", - "default": false, - "type": "boolean" - }, - "context": { - "description": "Send the context", - "default": false, - "type": "boolean" - }, - "headers": { - "description": "Send the headers", - "default": false, - "type": "boolean" - }, - "sdl": { - "description": "Send the SDL", - "default": false, - "type": "boolean" - }, - "status_code": { - "description": "Send the HTTP status", - "default": false, - "type": "boolean" - } - }, - "additionalProperties": false - } - } + "type": "array" }, - "subgraph": { - "description": "The subgraph stage request/response configuration", - "default": { - "all": { - "request": { - "headers": false, - "context": false, - "body": false, - "uri": false, - "method": false, - "service_name": false - }, - "response": { - "headers": false, - "context": false, - "body": false, - "service_name": false, - "status_code": false - } - } - }, - "type": "object", - "properties": { - "all": { - "description": "What information is passed to a subgraph request/response stage", - "default": { - "request": { - "headers": false, - "context": false, - "body": false, - "uri": false, - "method": false, - "service_name": false - }, - "response": { - "headers": false, - "context": false, - "body": false, - "service_name": false, - "status_code": false - } - }, - "type": "object", - "properties": { - "request": { - "description": "What information is passed to a subgraph request/response stage", - "default": { - "headers": false, - "context": false, - "body": false, - "uri": false, - "method": false, - "service_name": false - }, - "type": "object", - "properties": { - "body": { - "description": "Send the body", - "default": false, - "type": "boolean" - }, - "context": { - "description": "Send the context", - "default": false, - "type": "boolean" - }, - "headers": { - "description": "Send the headers", - "default": false, - "type": "boolean" - }, - "method": { - "description": "Send the method URI", - "default": false, - "type": "boolean" - }, - "service_name": { - "description": "Send the service name", - "default": false, - "type": "boolean" - }, - "uri": { - "description": "Send the subgraph URI", - "default": false, - "type": "boolean" - } - }, - "additionalProperties": false - }, - "response": { - "description": "What information is passed to a subgraph request/response stage", - "default": { - "headers": false, - "context": false, - "body": false, - "service_name": false, - "status_code": false - }, - "type": "object", - "properties": { - "body": { - "description": "Send the body", - "default": false, - "type": "boolean" - }, - "context": { - "description": "Send the context", - "default": false, - "type": "boolean" - }, - "headers": { - "description": "Send the headers", - "default": false, - "type": "boolean" - }, - "service_name": { - "description": "Send the service name", - "default": false, - "type": "boolean" - }, - "status_code": { - "description": "Send the http status", - "default": false, - "type": "boolean" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } + { + "description": "Array of integers", + "items": { + "format": "int64", + "type": "integer" }, - "additionalProperties": false + "type": "array" }, - "supergraph": { - "description": "The supergraph stage request/response configuration", - "default": { - "request": { - "headers": false, - "context": false, - "body": false, - "sdl": false, - "method": false - }, - "response": { - "headers": false, - "context": false, - "body": false, - "sdl": false, - "status_code": false - } + { + "description": "Array of floats", + "items": { + "format": "double", + "type": "number" }, - "type": "object", - "properties": { - "request": { - "description": "The request configuration", - "default": { - "headers": false, - "context": false, - "body": false, - "sdl": false, - "method": false - }, - "type": "object", - "properties": { - "body": { - "description": "Send the body", - "default": false, - "type": "boolean" - }, - "context": { - "description": "Send the context", - "default": false, - "type": "boolean" - }, - "headers": { - "description": "Send the headers", - "default": false, - "type": "boolean" - }, - "method": { - "description": "Send the method", - "default": false, - "type": "boolean" - }, - "sdl": { - "description": "Send the SDL", - "default": false, - "type": "boolean" - } - }, - "additionalProperties": false - }, - "response": { - "description": "What information is passed to a router request/response stage", - "default": { - "headers": false, - "context": false, - "body": false, - "sdl": false, - "status_code": false - }, - "type": "object", - "properties": { - "body": { - "description": "Send the body", - "default": false, - "type": "boolean" - }, - "context": { - "description": "Send the context", - "default": false, - "type": "boolean" - }, - "headers": { - "description": "Send the headers", - "default": false, - "type": "boolean" - }, - "sdl": { - "description": "Send the SDL", - "default": false, - "type": "boolean" - }, - "status_code": { - "description": "Send the HTTP status", - "default": false, - "type": "boolean" - } - }, - "additionalProperties": false - } - } + "type": "array" }, - "timeout": { - "description": "The timeout for external requests", - "default": { - "secs": 1, - "nanos": 0 + { + "description": "Array of strings", + "items": { + "type": "string" }, - "type": "string" - }, - "url": { - "description": "The url you'd like to offload processing to", - "type": "string" + "type": "array" } - }, - "additionalProperties": false + ] }, - "cors": { - "description": "Cross origin request headers.", - "default": { - "allow_any_origin": false, - "allow_credentials": false, - "allow_headers": [], - "expose_headers": null, - "origins": [ - "https://studio.apollographql.com" - ], - "match_origins": null, - "methods": [ - "GET", - "POST", - "OPTIONS" - ], - "max_age": null - }, - "type": "object", - "properties": { - "allow_any_origin": { - "description": "Set to true to allow any origin.\n\nDefaults to false Having this set to true is the only way to allow Origin: null.", - "default": false, + "AttributeValue": { + "anyOf": [ + { + "description": "bool values", "type": "boolean" }, - "allow_credentials": { - "description": "Set to true to add the `Access-Control-Allow-Credentials` header.", - "default": false, - "type": "boolean" + { + "description": "i64 values", + "format": "int64", + "type": "integer" }, - "allow_headers": { - "description": "The headers to allow.\n\nIf this value is not set, the router will mirror client's `Access-Control-Request-Headers`.\n\nNote that if you set headers here, you also want to have a look at your `CSRF` plugins configuration, and make sure you either: - accept `x-apollo-operation-name` AND / OR `apollo-require-preflight` - defined `csrf` required headers in your yml configuration, as shown in the `examples/cors-and-csrf/custom-headers.router.yaml` files.", - "default": [], - "type": "array", - "items": { - "type": "string" - } + { + "description": "f64 values", + "format": "double", + "type": "number" }, - "expose_headers": { - "description": "Which response headers should be made available to scripts running in the browser, in response to a cross-origin request.", - "type": "array", - "items": { - "type": "string" - }, - "nullable": true + { + "description": "String values", + "type": "string" }, - "match_origins": { - "description": "`Regex`es you want to match the origins against to determine if they're allowed. Defaults to an empty list. Note that `origins` will be evaluated before `match_origins`", - "type": "array", + { + "$ref": "#/definitions/AttributeArray", + "description": "#/definitions/AttributeArray" + } + ] + }, + "AttributesForwardConf": { + "additionalProperties": false, + "description": "Configuration to add custom attributes/labels on metrics to subgraphs", + "properties": { + "context": { + "description": "Configuration to forward values from the context to custom attributes/labels in metrics", "items": { - "type": "string" + "$ref": "#/definitions/ContextForward", + "description": "#/definitions/ContextForward" }, - "nullable": true + "type": "array" }, - "max_age": { - "description": "The `Access-Control-Max-Age` header value in time units", - "type": "string" + "errors": { + "$ref": "#/definitions/ErrorsForward", + "description": "#/definitions/ErrorsForward" }, - "methods": { - "description": "Allowed request methods. Defaults to GET, POST, OPTIONS.", - "default": [ - "GET", - "POST", - "OPTIONS" - ], - "type": "array", - "items": { - "type": "string" - } + "request": { + "$ref": "#/definitions/Forward", + "description": "#/definitions/Forward" }, - "origins": { - "description": "The origin(s) to allow requests from. Defaults to `https://studio.apollographql.com/` for Apollo Studio.", - "default": [ - "https://studio.apollographql.com" - ], - "type": "array", + "response": { + "$ref": "#/definitions/Forward", + "description": "#/definitions/Forward" + }, + "static": { + "description": "Configuration to insert custom attributes/labels in metrics", "items": { - "type": "string" - } + "$ref": "#/definitions/Insert2", + "description": "#/definitions/Insert2" + }, + "type": "array" } }, - "additionalProperties": false + "type": "object" }, - "csrf": { - "description": "CSRF Configuration.", - "type": "object", - "properties": { - "required_headers": { - "description": "Override the headers to check for by setting custom_headers Note that if you set required_headers here, you may also want to have a look at your `CORS` configuration, and make sure you either: - did not set any `allow_headers` list (so it defaults to `mirror_request`) - added your required headers to the allow_headers list, as shown in the `examples/cors-and-csrf/custom-headers.router.yaml` files.", - "default": [ - "x-apollo-operation-name", - "apollo-require-preflight" + "AuthConfig": { + "oneOf": [ + { + "additionalProperties": false, + "properties": { + "aws_sig_v4": { + "$ref": "#/definitions/AWSSigV4Config", + "description": "#/definitions/AWSSigV4Config" + } + }, + "required": [ + "aws_sig_v4" ], - "type": "array", - "items": { - "type": "string" - } - }, - "unsafe_disabled": { - "description": "The CSRF plugin is enabled by default; set unsafe_disabled = true to disable the plugin behavior Note that setting this to true is deemed unsafe. See .", - "default": false, - "type": "boolean" + "type": "object" } - }, - "additionalProperties": false + ] }, - "experimental_api_schema_generation_mode": { - "description": "Set the API schema generation implementation to use.", - "default": "both", - "oneOf": [ + "Auto": { + "enum": [ + "auto" + ], + "type": "string" + }, + "AvailableParallelism": { + "anyOf": [ { - "description": "Use the new Rust-based implementation.", - "type": "string", - "enum": [ - "new" - ] + "$ref": "#/definitions/Auto", + "description": "#/definitions/Auto" }, { - "description": "Use the old JavaScript-based implementation.", - "type": "string", - "enum": [ - "legacy" - ] - }, - { - "description": "Use Rust-based and Javascript-based implementations side by side, logging warnings if the implementations disagree.", - "type": "string", - "enum": [ - "both" - ] + "format": "uint", + "minimum": 1.0, + "type": "integer" } ] }, - "experimental_apollo_metrics_generation_mode": { - "description": "Set the Apollo usage report signature and referenced field generation implementation to use.", - "default": "both", - "oneOf": [ - { - "description": "Use the new Rust-based implementation.", - "type": "string", - "enum": [ - "new" - ] + "BatchProcessorConfig": { + "description": "Batch processor configuration", + "properties": { + "max_concurrent_exports": { + "default": 1, + "description": "Maximum number of concurrent exports\n\nLimits the number of spawned tasks for exports and thus memory consumed by an exporter. A value of 1 will cause exports to be performed synchronously on the BatchSpanProcessor task. The default is 1.", + "format": "uint", + "minimum": 0.0, + "type": "integer" }, - { - "description": "Use the old JavaScript-based implementation.", - "type": "string", - "enum": [ - "legacy" - ] + "max_export_batch_size": { + "default": 512, + "description": "The maximum number of spans to process in a single batch. If there are more than one batch worth of spans then it processes multiple batches of spans one batch after the other without any delay. The default value is 512.", + "format": "uint", + "minimum": 0.0, + "type": "integer" }, - { - "description": "Use Rust-based and Javascript-based implementations side by side, logging warnings if the implementations disagree.", - "type": "string", - "enum": [ - "both" - ] - } - ] - }, - "experimental_chaos": { - "description": "Configuration for chaos testing, trying to reproduce bugs that require uncommon conditions. You probably don’t want this in production!", - "default": { - "force_reload": null - }, - "type": "object", - "properties": { - "force_reload": { - "description": "Force a hot reload of the Router (as if the schema or configuration had changed) at a regular time interval.", - "type": "string", - "nullable": true + "max_export_timeout": { + "default": { + "nanos": 0, + "secs": 30 + }, + "description": "The maximum duration to export a batch of data. The default value is 30 seconds.", + "type": "string" + }, + "max_queue_size": { + "default": 2048, + "description": "The maximum queue size to buffer spans for delayed processing. If the queue gets full it drops the spans. The default value of is 2048.", + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "scheduled_delay": { + "default": { + "nanos": 0, + "secs": 5 + }, + "description": "The delay interval in milliseconds between two consecutive processing of batches. The default value is 5 seconds.", + "type": "string" } }, - "additionalProperties": false + "type": "object" }, - "experimental_demand_control": { - "description": "Demand control configuration", - "type": "object", - "required": [ - "enabled", - "mode", - "strategy" - ], + "Batching": { + "additionalProperties": false, + "description": "Configuration for Batching", "properties": { "enabled": { - "description": "Enable demand control", + "default": false, + "description": "Activates Batching (disabled by default)", "type": "boolean" }, "mode": { - "description": "The mode that the demand control plugin should operate in. - Measure: The plugin will measure the cost of incoming requests but not reject them. - Enforce: The plugin will enforce the cost of incoming requests and reject them if the algorithm indicates that they should be rejected.", - "type": "string", - "enum": [ - "measure", - "enforce" - ] + "$ref": "#/definitions/BatchingMode", + "description": "#/definitions/BatchingMode" }, - "strategy": { - "description": "The strategy used to reject requests.", - "oneOf": [ - { - "description": "A simple, statically-defined cost mapping for operations and types.\n\nOperation costs: - Mutation: 10 - Query: 0 - Subscription 0\n\nType costs: - Object: 1 - Interface: 1 - Union: 1 - Scalar: 0 - Enum: 0", - "type": "object", - "required": [ - "static_estimated" - ], - "properties": { - "static_estimated": { - "type": "object", - "required": [ - "list_size", - "max" - ], - "properties": { - "list_size": { - "description": "The assumed length of lists returned by the operation.", - "type": "integer", - "format": "uint32", - "minimum": 0.0 - }, - "max": { - "description": "The maximum cost of a query", - "type": "number", - "format": "double" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "test" - ], - "properties": { - "test": { - "type": "object", - "required": [ - "error", - "stage" - ], - "properties": { - "error": { - "type": "string", - "enum": [ - "estimated_cost_too_expensive", - "actual_cost_too_expensive" - ] - }, - "stage": { - "type": "string", - "enum": [ - "execution_request", - "execution_response", - "subgraph_request", - "subgraph_response" - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] + "subgraph": { + "$ref": "#/definitions/SubgraphConfiguration_for_CommonBatchingConfig", + "description": "#/definitions/SubgraphConfiguration_for_CommonBatchingConfig", + "nullable": true } }, - "additionalProperties": false + "required": [ + "mode" + ], + "type": "object" }, - "experimental_query_planner_mode": { - "description": "Set the query planner implementation to use.", - "default": "legacy", + "BatchingMode": { "oneOf": [ { - "description": "Use the new Rust-based implementation.", - "type": "string", - "enum": [ - "new" - ] - }, - { - "description": "Use the old JavaScript-based implementation.", - "type": "string", - "enum": [ - "legacy" - ] - }, - { - "description": "Use Rust-based and Javascript-based implementations side by side, logging warnings if the implementations disagree.", - "type": "string", + "description": "batch_http_link", "enum": [ - "both" - ] + "batch_http_link" + ], + "type": "string" } ] }, - "experimental_type_conditioned_fetching": { - "description": "Type conditioned fetching configuration.", - "default": false, - "type": "boolean" - }, - "forbid_mutations": { - "description": "Forbid mutations configuration", - "type": "boolean" + "BodyForward": { + "additionalProperties": false, + "description": "Configuration to forward body values in metric attributes/labels", + "properties": { + "default": { + "$ref": "#/definitions/AttributeValue", + "description": "#/definitions/AttributeValue", + "nullable": true + }, + "name": { + "description": "The name of the attribute", + "type": "string" + }, + "path": { + "description": "The path in the body", + "type": "string" + } + }, + "required": [ + "name", + "path" + ], + "type": "object" }, - "headers": { - "description": "Configuration for header propagation", - "type": "object", + "CSRFConfig": { + "additionalProperties": false, + "description": "CSRF Configuration.", "properties": { - "all": { - "description": "Rules to apply to all subgraphs", - "type": "object", - "required": [ - "request" + "required_headers": { + "default": [ + "x-apollo-operation-name", + "apollo-require-preflight" ], - "properties": { - "request": { - "description": "Propagate/Insert/Remove headers from request", - "type": "array", - "items": { - "oneOf": [ - { - "type": "object", - "required": [ - "insert" - ], - "properties": { - "insert": { - "description": "Insert header", - "anyOf": [ - { - "description": "Insert static header", - "type": "object", - "required": [ - "name", - "value" - ], - "properties": { - "name": { - "description": "The name of the header", - "type": "string" - }, - "value": { - "description": "The value for the header", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Insert header with a value coming from context key (works only for a string in the context)", - "type": "object", - "required": [ - "from_context", - "name" - ], - "properties": { - "from_context": { - "description": "Specify context key to fetch value", - "type": "string" - }, - "name": { - "description": "Specify header name", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Insert header with a value coming from body", - "type": "object", - "required": [ - "name", - "path" - ], - "properties": { - "default": { - "description": "The default if the path in the body did not resolve to an element", - "type": "string", - "nullable": true - }, - "name": { - "description": "The target header name", - "type": "string" - }, - "path": { - "description": "The path in the request body", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "remove" - ], - "properties": { - "remove": { - "description": "Remove header", - "oneOf": [ - { - "description": "Remove a header given a header name", - "type": "object", - "required": [ - "named" - ], - "properties": { - "named": { - "description": "Remove a header given a header name", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Remove a header given a regex matching header name", - "type": "object", - "required": [ - "matching" - ], - "properties": { - "matching": { - "description": "Remove a header given a regex matching against the header name", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "propagate" - ], - "properties": { - "propagate": { - "description": "Propagate header", - "anyOf": [ - { - "description": "Propagate header given a header name", - "type": "object", - "required": [ - "named" - ], - "properties": { - "default": { - "description": "Default value for the header.", - "type": "string", - "nullable": true - }, - "named": { - "description": "The source header name", - "type": "string" - }, - "rename": { - "description": "An optional target header name", - "type": "string", - "nullable": true - } - }, - "additionalProperties": false - }, - { - "description": "Propagate header given a regex to match header name", - "type": "object", - "required": [ - "matching" - ], - "properties": { - "matching": { - "description": "The regex on header name", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - } - ] - } - } + "description": "Override the headers to check for by setting custom_headers Note that if you set required_headers here, you may also want to have a look at your `CORS` configuration, and make sure you either: - did not set any `allow_headers` list (so it defaults to `mirror_request`) - added your required headers to the allow_headers list, as shown in the `examples/cors-and-csrf/custom-headers.router.yaml` files.", + "items": { + "type": "string" }, - "additionalProperties": false, - "nullable": true + "type": "array" }, - "subgraphs": { - "description": "Rules to specific subgraphs", - "type": "object", - "additionalProperties": { - "type": "object", - "required": [ - "request" - ], - "properties": { - "request": { - "description": "Propagate/Insert/Remove headers from request", - "type": "array", - "items": { - "oneOf": [ - { - "type": "object", - "required": [ - "insert" - ], - "properties": { - "insert": { - "description": "Insert header", - "anyOf": [ - { - "description": "Insert static header", - "type": "object", - "required": [ - "name", - "value" - ], - "properties": { - "name": { - "description": "The name of the header", - "type": "string" - }, - "value": { - "description": "The value for the header", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Insert header with a value coming from context key (works only for a string in the context)", - "type": "object", - "required": [ - "from_context", - "name" - ], - "properties": { - "from_context": { - "description": "Specify context key to fetch value", - "type": "string" - }, - "name": { - "description": "Specify header name", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Insert header with a value coming from body", - "type": "object", - "required": [ - "name", - "path" - ], - "properties": { - "default": { - "description": "The default if the path in the body did not resolve to an element", - "type": "string", - "nullable": true - }, - "name": { - "description": "The target header name", - "type": "string" - }, - "path": { - "description": "The path in the request body", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "remove" - ], - "properties": { - "remove": { - "description": "Remove header", - "oneOf": [ - { - "description": "Remove a header given a header name", - "type": "object", - "required": [ - "named" - ], - "properties": { - "named": { - "description": "Remove a header given a header name", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Remove a header given a regex matching header name", - "type": "object", - "required": [ - "matching" - ], - "properties": { - "matching": { - "description": "Remove a header given a regex matching against the header name", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "propagate" - ], - "properties": { - "propagate": { - "description": "Propagate header", - "anyOf": [ - { - "description": "Propagate header given a header name", - "type": "object", - "required": [ - "named" - ], - "properties": { - "default": { - "description": "Default value for the header.", - "type": "string", - "nullable": true - }, - "named": { - "description": "The source header name", - "type": "string" - }, - "rename": { - "description": "An optional target header name", - "type": "string", - "nullable": true - } - }, - "additionalProperties": false - }, - { - "description": "Propagate header given a regex to match header name", - "type": "object", - "required": [ - "matching" - ], - "properties": { - "matching": { - "description": "The regex on header name", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - } - ] - } - } - }, - "additionalProperties": false - } + "unsafe_disabled": { + "default": false, + "description": "The CSRF plugin is enabled by default; set unsafe_disabled = true to disable the plugin behavior Note that setting this to true is deemed unsafe. See .", + "type": "boolean" } }, - "additionalProperties": false + "type": "object" }, - "health_check": { - "description": "Health check configuration", - "default": { - "listen": "127.0.0.1:8088", - "enabled": true, - "path": "/health" + "Cache": { + "additionalProperties": false, + "description": "Cache configuration", + "properties": { + "in_memory": { + "$ref": "#/definitions/InMemoryCache", + "description": "#/definitions/InMemoryCache" + }, + "redis": { + "$ref": "#/definitions/RedisCache", + "description": "#/definitions/RedisCache", + "nullable": true + } }, - "type": "object", + "type": "object" + }, + "CallbackMode": { + "additionalProperties": false, + "description": "Using a callback url", "properties": { - "enabled": { - "description": "Set to false to disable the health check", - "default": true, - "type": "boolean" + "heartbeat_interval": { + "$ref": "#/definitions/HeartbeatInterval", + "description": "#/definitions/HeartbeatInterval" }, "listen": { - "description": "The socket address and port to listen on Defaults to 127.0.0.1:8088", - "default": "127.0.0.1:8088", - "anyOf": [ - { - "description": "Socket address.", - "type": "string" - }, - { - "description": "Unix socket.", - "type": "string" - } - ] + "$ref": "#/definitions/ListenAddr", + "description": "#/definitions/ListenAddr", + "nullable": true }, "path": { - "description": "Optionally set a custom healthcheck path Defaults to /health", - "default": "/health", + "description": "Specify on which path you want to listen for callbacks (default: /callback)", + "nullable": true, + "type": "string", + "writeOnly": true + }, + "public_url": { + "description": "URL used to access this router instance, including the path configured on the Router", "type": "string" + }, + "subgraphs": { + "default": [], + "description": "Specify on which subgraph we enable the callback mode for subscription If empty it applies to all subgraphs (passthrough mode takes precedence)", + "items": { + "type": "string" + }, + "type": "array", + "uniqueItems": true } }, - "additionalProperties": false + "required": [ + "public_url" + ], + "type": "object" }, - "homepage": { - "description": "Homepage configuration", - "default": { - "enabled": true, - "graph_ref": null + "Chaos": { + "additionalProperties": false, + "description": "Configuration for chaos testing, trying to reproduce bugs that require uncommon conditions. You probably don’t want this in production!", + "properties": { + "force_reload": { + "description": "Force a hot reload of the Router (as if the schema or configuration had changed) at a regular time interval.", + "nullable": true, + "type": "string" + } }, - "type": "object", + "type": "object" + }, + "CollectorConfig": { + "additionalProperties": false, "properties": { - "enabled": { - "description": "Set to false to disable the homepage", - "default": true, - "type": "boolean" + "endpoint": { + "$ref": "#/definitions/UriEndpoint", + "description": "#/definitions/UriEndpoint" }, - "graph_ref": { - "description": "Graph reference This will allow you to redirect from the Apollo Router landing page back to Apollo Studio Explorer", - "type": "string", - "nullable": true + "password": { + "description": "The optional password", + "nullable": true, + "type": "string" + }, + "username": { + "description": "The optional username", + "nullable": true, + "type": "string" } }, - "additionalProperties": false + "type": "object" }, - "include_subgraph_errors": { - "description": "Configuration for exposing errors that originate from subgraphs", - "type": "object", + "CommonBatchingConfig": { + "description": "Common options for configuring subgraph batching", "properties": { - "all": { - "description": "Include errors from all subgraphs", - "default": false, + "enabled": { + "description": "Whether this batching config should be enabled", "type": "boolean" - }, - "subgraphs": { - "description": "Include errors from specific subgraphs", - "default": {}, - "type": "object", - "additionalProperties": { - "type": "boolean" - } } }, - "additionalProperties": false + "required": [ + "enabled" + ], + "type": "object" }, - "limits": { - "description": "Configuration for operation limits, parser limits, HTTP limits, etc.", - "default": { - "max_depth": null, - "max_height": null, - "max_root_fields": null, - "max_aliases": null, - "warn_only": false, - "parser_max_recursion": 500, - "parser_max_tokens": 15000, - "http_max_request_bytes": 2000000 - }, - "type": "object", - "properties": { - "http_max_request_bytes": { - "description": "Limit the size of incoming HTTP requests read from the network, to protect against running out of memory. Default: 2000000 (2 MB)", - "default": 2000000, - "type": "integer", - "format": "uint", - "minimum": 0.0 + "Compression": { + "oneOf": [ + { + "description": "gzip", + "enum": [ + "gzip" + ], + "type": "string" }, - "max_aliases": { - "description": "If set, requests with operations with more aliases than this maximum are rejected with a HTTP 400 Bad Request response and GraphQL error with `\"extensions\": {\"code\": \"MAX_ALIASES_LIMIT\"}`", - "type": "integer", - "format": "uint32", - "minimum": 0.0, - "nullable": true + { + "description": "deflate", + "enum": [ + "deflate" + ], + "type": "string" }, - "max_depth": { - "description": "If set, requests with operations deeper than this maximum are rejected with a HTTP 400 Bad Request response and GraphQL error with `\"extensions\": {\"code\": \"MAX_DEPTH_LIMIT\"}`\n\nCounts depth of an operation, looking at its selection sets, including fields in fragments and inline fragments. The following example has a depth of 3.\n\n```graphql query getProduct { book { # 1 ...bookDetails } }\n\nfragment bookDetails on Book { details { # 2 ... on ProductDetailsBook { country # 3 } } } ```", - "type": "integer", - "format": "uint32", - "minimum": 0.0, - "nullable": true + { + "description": "brotli", + "enum": [ + "br" + ], + "type": "string" }, - "max_height": { - "description": "If set, requests with operations higher than this maximum are rejected with a HTTP 400 Bad Request response and GraphQL error with `\"extensions\": {\"code\": \"MAX_DEPTH_LIMIT\"}`\n\nHeight is based on simple merging of fields using the same name or alias, but only within the same selection set. For example `name` here is only counted once and the query has height 3, not 4:\n\n```graphql query { name { first } name { last } } ```\n\nThis may change in a future version of Apollo Router to do [full field merging across fragments][merging] instead.\n\n[merging]: https://spec.graphql.org/October2021/#sec-Field-Selection-Merging]", - "type": "integer", - "format": "uint32", - "minimum": 0.0, - "nullable": true - }, - "max_root_fields": { - "description": "If set, requests with operations with more root fields than this maximum are rejected with a HTTP 400 Bad Request response and GraphQL error with `\"extensions\": {\"code\": \"MAX_ROOT_FIELDS_LIMIT\"}`\n\nThis limit counts only the top level fields in a selection set, including fragments and inline fragments.", - "type": "integer", - "format": "uint32", - "minimum": 0.0, - "nullable": true - }, - "parser_max_recursion": { - "description": "Limit recursion in the GraphQL parser to protect against stack overflow. default: 500", - "default": 500, - "type": "integer", - "format": "uint", - "minimum": 0.0 - }, - "parser_max_tokens": { - "description": "Limit the number of tokens the GraphQL parser processes before aborting.", - "default": 15000, - "type": "integer", - "format": "uint", - "minimum": 0.0 - }, - "warn_only": { - "description": "If set to true (which is the default is dev mode), requests that exceed a `max_*` limit are *not* rejected. Instead they are executed normally, and a warning is logged.", - "default": false, - "type": "boolean" - } - }, - "additionalProperties": false - }, - "override_subgraph_url": { - "description": "Subgraph URL mappings", - "anyOf": [ { - "description": "Subgraph URL mappings", - "type": "object", - "additionalProperties": { - "type": "string" - } + "description": "identity", + "enum": [ + "identity" + ], + "type": "string" } ] }, - "persisted_queries": { - "description": "Configures managed persisted queries", - "default": { - "enabled": false, - "log_unknown": false, - "safelist": { - "enabled": false, - "require_id": false - } - }, - "type": "object", - "properties": { - "enabled": { - "description": "Activates Persisted Queries (disabled by default)", - "default": false, - "type": "boolean" - }, - "log_unknown": { - "description": "Enabling this field configures the router to log any freeform GraphQL request that is not in the persisted query list", - "default": false, - "type": "boolean" - }, - "safelist": { - "description": "Restricts execution of operations that are not found in the Persisted Query List", - "default": { - "enabled": false, - "require_id": false - }, - "type": "object", + "Condition_for_RouterSelector": { + "oneOf": [ + { + "additionalProperties": false, + "description": "A condition to check a selection against a value.", "properties": { - "enabled": { - "description": "Enables using the persisted query list as a safelist (disabled by default)", - "default": false, - "type": "boolean" - }, - "require_id": { - "description": "Enabling this field configures the router to reject any request that does not include the persisted query ID", - "default": false, - "type": "boolean" + "eq": { + "items": { + "$ref": "#/definitions/SelectorOrValue_for_RouterSelector", + "description": "#/definitions/SelectorOrValue_for_RouterSelector" + }, + "maxItems": 2, + "minItems": 2, + "type": "array" } }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - "plugins": { - "description": "Plugin configuration", - "properties": { - "experimental.broken": { - "description": "This is a broken plugin for testing purposes only.", - "type": "object", "required": [ - "enabled" + "eq" ], + "type": "object" + }, + { + "additionalProperties": false, + "description": "The first selection must be greater than the second selection.", "properties": { - "enabled": { - "description": "Enable the broken plugin.", - "type": "boolean" + "gt": { + "items": { + "$ref": "#/definitions/SelectorOrValue_for_RouterSelector", + "description": "#/definitions/SelectorOrValue_for_RouterSelector" + }, + "maxItems": 2, + "minItems": 2, + "type": "array" } - } - }, - "experimental.expose_query_plan": { - "description": "Expose query plan", - "type": "boolean" - }, - "experimental.record": { - "description": "Request recording configuration.", - "type": "object", + }, "required": [ - "enabled" + "gt" ], + "type": "object" + }, + { + "additionalProperties": false, + "description": "The first selection must be less than the second selection.", "properties": { - "enabled": { - "description": "The recording plugin is disabled by default.", - "type": "boolean" - }, - "storage_path": { - "description": "The path to the directory where recordings will be stored. Defaults to the current working directory.", - "type": "string", - "nullable": true + "lt": { + "items": { + "$ref": "#/definitions/SelectorOrValue_for_RouterSelector", + "description": "#/definitions/SelectorOrValue_for_RouterSelector" + }, + "maxItems": 2, + "minItems": 2, + "type": "array" } }, - "additionalProperties": false - }, - "experimental.restricted": { - "description": "Restricted plugin (for testing purposes only)", - "type": "object", "required": [ - "enabled" + "lt" ], + "type": "object" + }, + { + "additionalProperties": false, + "description": "A condition to check a selection against a selector.", "properties": { - "enabled": { - "description": "Enable the restricted plugin (for testing purposes only)", - "type": "boolean" + "exists": { + "$ref": "#/definitions/RouterSelector", + "description": "#/definitions/RouterSelector" } - } - }, - "test.always_fails_to_start": { - "description": "Configuration for the test plugin", - "type": "object", + }, "required": [ - "name" + "exists" ], + "type": "object" + }, + { + "additionalProperties": false, + "description": "All sub-conditions must be true.", "properties": { - "name": { - "description": "The name of the test", - "type": "string" + "all": { + "items": { + "$ref": "#/definitions/Condition_for_RouterSelector", + "description": "#/definitions/Condition_for_RouterSelector" + }, + "type": "array" } - } - }, - "test.always_starts_and_stops": { - "description": "Configuration for the test plugin", - "type": "object", + }, "required": [ - "name" + "all" ], - "properties": { - "name": { - "description": "The name of the test", - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - "preview_entity_cache": { - "description": "Configuration for entity caching", - "type": "object", - "required": [ - "redis" - ], - "properties": { - "enabled": { - "description": "activates caching for all subgraphs, unless overriden in subgraph specific configuration", - "type": "boolean", - "nullable": true + "type": "object" }, - "metrics": { - "description": "Entity caching evaluation metrics", - "type": "object", + { + "additionalProperties": false, + "description": "At least one sub-conditions must be true.", "properties": { - "enabled": { - "description": "enables metrics evaluating the benefits of entity caching", - "default": false, - "type": "boolean" - }, - "separate_per_type": { - "description": "Adds the entity type name to attributes. This can greatly increase the cardinality", - "default": false, - "type": "boolean" - }, - "ttl": { - "description": "Metrics counter TTL", - "type": "string", - "nullable": true + "any": { + "items": { + "$ref": "#/definitions/Condition_for_RouterSelector", + "description": "#/definitions/Condition_for_RouterSelector" + }, + "type": "array" } }, - "additionalProperties": false - }, - "redis": { - "description": "Redis cache configuration", - "type": "object", "required": [ - "urls" + "any" ], + "type": "object" + }, + { + "additionalProperties": false, + "description": "The sub-condition must not be true", "properties": { - "namespace": { - "description": "namespace used to prefix Redis keys", - "type": "string", - "nullable": true - }, - "password": { - "description": "Redis password if not provided in the URLs. This field takes precedence over the password in the URL", - "type": "string", - "nullable": true - }, - "required_to_start": { - "description": "Prevents the router from starting if it cannot connect to Redis", - "default": false, - "type": "boolean" - }, - "reset_ttl": { - "description": "When a TTL is set on a key, reset it when reading the data from that key", - "default": true, - "type": "boolean" - }, - "timeout": { - "description": "Redis request timeout (default: 2ms)", - "type": "string", - "nullable": true - }, - "tls": { - "description": "TLS client configuration", - "type": "object", - "properties": { - "certificate_authorities": { - "description": "list of certificate authorities in PEM format", - "type": "string", - "nullable": true - }, - "client_authentication": { - "description": "client certificate authentication", - "type": "object", - "required": [ - "certificate_chain", - "key" - ], - "properties": { - "certificate_chain": { - "description": "list of certificates in PEM format", - "writeOnly": true, - "type": "string" - }, - "key": { - "description": "key in PEM format", - "writeOnly": true, - "type": "string" - } - }, - "additionalProperties": false, - "nullable": true - } - }, - "additionalProperties": false, - "nullable": true - }, - "ttl": { - "description": "TTL for entries", - "type": "string", - "nullable": true - }, - "urls": { - "description": "List of URLs to the Redis cluster", - "type": "array", - "items": { - "type": "string", - "format": "uri" - } - }, - "username": { - "description": "Redis username if not provided in the URLs. This field takes precedence over the username in the URL", - "type": "string", - "nullable": true + "not": { + "$ref": "#/definitions/Condition_for_RouterSelector", + "description": "#/definitions/Condition_for_RouterSelector" } }, - "additionalProperties": false + "required": [ + "not" + ], + "type": "object" }, - "subgraphs": { - "description": "Per subgraph configuration", - "type": "object", - "additionalProperties": { - "description": "Per subgraph configuration for entity caching", - "type": "object", - "properties": { - "enabled": { - "description": "activates caching for this subgraph, overrides the global configuration", - "type": "boolean", - "nullable": true - }, - "private_id": { - "description": "Context key used to separate cache sections per user", - "type": "string", - "nullable": true - }, - "ttl": { - "description": "expiration for all keys for this subgraph, unless overriden by the `Cache-Control` header in subgraph responses", - "type": "string", - "nullable": true - } - }, - "additionalProperties": false - } - } - }, - "additionalProperties": false - }, - "preview_file_uploads": { - "description": "Configuration for File Uploads plugin", - "type": "object", - "required": [ - "enabled", - "protocols" - ], - "properties": { - "enabled": { - "description": "Whether the file upload plugin should be enabled (default: false)", - "type": "boolean" + { + "description": "Static true condition", + "enum": [ + "true" + ], + "type": "string" }, - "protocols": { - "description": "Supported protocol configurations for file uploads", - "type": "object", - "required": [ - "multipart" + { + "description": "Static false condition", + "enum": [ + "false" ], + "type": "string" + } + ] + }, + "Condition_for_SubgraphSelector": { + "oneOf": [ + { + "additionalProperties": false, + "description": "A condition to check a selection against a value.", "properties": { - "multipart": { - "description": "Configuration for multipart requests.\n\nThis protocol conforms to [jaydenseric's multipart spec](https://github.com/jaydenseric/graphql-multipart-request-spec)", - "type": "object", - "properties": { - "enabled": { - "description": "Whether to enable the multipart protocol for file uploads (default: true)", - "default": true, - "type": "boolean" - }, - "limits": { - "description": "Resource limits for multipart requests", - "type": "object", - "required": [ - "max_file_size", - "max_files" - ], - "properties": { - "max_file_size": { - "description": "The maximum size of each file, in bytes (default: 5MB)", - "type": "string" - }, - "max_files": { - "description": "The maximum amount of files allowed for a single query (default: 4)", - "type": "integer", - "format": "uint", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - "mode": { - "description": "The supported mode for the request (default: [MultipartRequestMode::Stream])", - "oneOf": [ - { - "description": "The multipart request will not be loaded into memory and instead will be streamed directly to the subgraph in the order received. This has some limitations, mainly that the query _must_ be able to be streamed directly to the subgraph without buffering.\n\nIn practice, this means that certain queries will fail due to ordering of the files.", - "type": "string", - "enum": [ - "stream" - ] - } - ] - } + "eq": { + "items": { + "$ref": "#/definitions/SelectorOrValue_for_SubgraphSelector", + "description": "#/definitions/SelectorOrValue_for_SubgraphSelector" }, - "additionalProperties": false + "maxItems": 2, + "minItems": 2, + "type": "array" } }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - "progressive_override": { - "description": "Configuration for the progressive override plugin", - "type": "object" - }, - "rhai": { - "description": "Configuration for the Rhai Plugin", - "type": "object", - "properties": { - "main": { - "description": "The main entry point for Rhai script evaluation", - "type": "string", - "nullable": true - }, - "scripts": { - "description": "The directory where Rhai scripts can be found", - "type": "string", - "nullable": true - } - }, - "additionalProperties": false - }, - "sandbox": { - "description": "Sandbox configuration", - "default": { - "enabled": false - }, - "type": "object", - "properties": { - "enabled": { - "description": "Set to true to enable sandbox", - "default": false, - "type": "boolean" - } - }, - "additionalProperties": false - }, - "subscription": { - "description": "Subscriptions configuration", - "type": "object", - "properties": { - "enable_deduplication": { - "description": "Enable the deduplication of subscription (for example if we detect the exact same request to subgraph we won't open a new websocket to the subgraph in passthrough mode) (default: true)", - "default": true, - "type": "boolean" - }, - "enabled": { - "description": "Enable subscription", - "default": true, - "type": "boolean" - }, - "max_opened_subscriptions": { - "description": "This is a limit to only have maximum X opened subscriptions at the same time. By default if it's not set there is no limit.", - "type": "integer", - "format": "uint", - "minimum": 0.0, - "nullable": true + "required": [ + "eq" + ], + "type": "object" }, - "mode": { - "description": "Select a subscription mode (callback or passthrough)", - "default": { - "callback": null, - "passthrough": null - }, - "type": "object", + { + "additionalProperties": false, + "description": "The first selection must be greater than the second selection.", "properties": { - "callback": { - "description": "Enable callback mode for subgraph(s)", - "type": "object", - "required": [ - "public_url" - ], - "properties": { - "heartbeat_interval": { - "description": "Heartbeat interval for callback mode (default: 5secs)", - "default": "enabled", - "anyOf": [ - { - "description": "disable heartbeat", - "type": "string", - "enum": [ - "disabled" - ] - }, - { - "description": "enable with default interval of 5s", - "type": "string", - "enum": [ - "enabled" - ] - }, - { - "description": "enable with custom interval, e.g. '100ms', '10s' or '1m'", - "type": "string" - } - ] - }, - "listen": { - "description": "Listen address on which the callback must listen (default: 127.0.0.1:4000)", - "writeOnly": true, - "anyOf": [ - { - "description": "Socket address.", - "type": "string" - }, - { - "description": "Unix socket.", - "type": "string" - } - ], - "nullable": true - }, - "path": { - "description": "Specify on which path you want to listen for callbacks (default: /callback)", - "writeOnly": true, - "type": "string", - "nullable": true - }, - "public_url": { - "description": "URL used to access this router instance, including the path configured on the Router", - "type": "string" - }, - "subgraphs": { - "description": "Specify on which subgraph we enable the callback mode for subscription If empty it applies to all subgraphs (passthrough mode takes precedence)", - "default": [], - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true - } - }, - "additionalProperties": false, - "nullable": true - }, - "passthrough": { - "description": "Enable passthrough mode for subgraph(s)", - "type": "object", - "properties": { - "all": { - "description": "Configuration for all subgraphs", - "type": "object", - "properties": { - "heartbeat_interval": { - "description": "Heartbeat interval for graphql-ws protocol (default: disabled)", - "default": "disabled", - "anyOf": [ - { - "description": "disable heartbeat", - "type": "string", - "enum": [ - "disabled" - ] - }, - { - "description": "enable with default interval of 5s", - "type": "string", - "enum": [ - "enabled" - ] - }, - { - "description": "enable with custom interval, e.g. '100ms', '10s' or '1m'", - "type": "string" - } - ] - }, - "path": { - "description": "Path on which WebSockets are listening", - "type": "string", - "nullable": true - }, - "protocol": { - "description": "Which WebSocket GraphQL protocol to use for this subgraph possible values are: 'graphql_ws' | 'graphql_transport_ws' (default: graphql_ws)", - "default": "graphql_ws", - "type": "string", - "enum": [ - "graphql_ws", - "graphql_transport_ws" - ] - } - }, - "additionalProperties": false, - "nullable": true - }, - "subgraphs": { - "description": "Configuration for specific subgraphs", - "default": {}, - "type": "object", - "additionalProperties": { - "description": "WebSocket configuration for a specific subgraph", - "type": "object", - "properties": { - "heartbeat_interval": { - "description": "Heartbeat interval for graphql-ws protocol (default: disabled)", - "default": "disabled", - "anyOf": [ - { - "description": "disable heartbeat", - "type": "string", - "enum": [ - "disabled" - ] - }, - { - "description": "enable with default interval of 5s", - "type": "string", - "enum": [ - "enabled" - ] - }, - { - "description": "enable with custom interval, e.g. '100ms', '10s' or '1m'", - "type": "string" - } - ] - }, - "path": { - "description": "Path on which WebSockets are listening", - "type": "string", - "nullable": true - }, - "protocol": { - "description": "Which WebSocket GraphQL protocol to use for this subgraph possible values are: 'graphql_ws' | 'graphql_transport_ws' (default: graphql_ws)", - "default": "graphql_ws", - "type": "string", - "enum": [ - "graphql_ws", - "graphql_transport_ws" - ] - } - }, - "additionalProperties": false - } - } + "gt": { + "items": { + "$ref": "#/definitions/SelectorOrValue_for_SubgraphSelector", + "description": "#/definitions/SelectorOrValue_for_SubgraphSelector" }, - "additionalProperties": false, - "nullable": true + "maxItems": 2, + "minItems": 2, + "type": "array" } }, - "additionalProperties": false + "required": [ + "gt" + ], + "type": "object" }, - "queue_capacity": { - "description": "It represent the capacity of the in memory queue to know how many events we can keep in a buffer", - "type": "integer", - "format": "uint", - "minimum": 0.0, - "nullable": true - } - }, - "additionalProperties": false - }, - "supergraph": { - "description": "Configuration for the supergraph", - "default": { - "listen": "127.0.0.1:4000", - "path": "/", - "introspection": false, - "experimental_reuse_query_fragments": null, - "generate_query_fragments": false, - "defer_support": true, - "query_planning": { - "cache": { - "in_memory": { - "limit": 512 - }, - "redis": null + { + "additionalProperties": false, + "description": "The first selection must be less than the second selection.", + "properties": { + "lt": { + "items": { + "$ref": "#/definitions/SelectorOrValue_for_SubgraphSelector", + "description": "#/definitions/SelectorOrValue_for_SubgraphSelector" + }, + "maxItems": 2, + "minItems": 2, + "type": "array" + } }, - "warmed_up_queries": null, - "experimental_plans_limit": null, - "experimental_paths_limit": null, - "experimental_reuse_query_plans": false, - "experimental_parallelism": 1 - }, - "early_cancel": false, - "experimental_log_on_broken_pipe": false - }, - "type": "object", - "properties": { - "defer_support": { - "description": "Set to false to disable defer support", - "default": true, - "type": "boolean" - }, - "early_cancel": { - "description": "abort request handling when the client drops the connection. Default: false. When set to true, some parts of the request pipeline like telemetry will not work properly, but request handling will stop immediately when the client connection is closed.", - "default": false, - "type": "boolean" - }, - "experimental_log_on_broken_pipe": { - "description": "Log a message if the client closes the connection before the response is sent. Default: false.", - "default": false, - "type": "boolean" + "required": [ + "lt" + ], + "type": "object" }, - "experimental_reuse_query_fragments": { - "description": "Enable reuse of query fragments Default: depends on the federation version", - "type": "boolean", - "nullable": true + { + "additionalProperties": false, + "description": "A condition to check a selection against a selector.", + "properties": { + "exists": { + "$ref": "#/definitions/SubgraphSelector", + "description": "#/definitions/SubgraphSelector" + } + }, + "required": [ + "exists" + ], + "type": "object" }, - "generate_query_fragments": { - "description": "Enable QP generation of fragments for subgraph requests Default: false", - "default": false, - "type": "boolean" + { + "additionalProperties": false, + "description": "All sub-conditions must be true.", + "properties": { + "all": { + "items": { + "$ref": "#/definitions/Condition_for_SubgraphSelector", + "description": "#/definitions/Condition_for_SubgraphSelector" + }, + "type": "array" + } + }, + "required": [ + "all" + ], + "type": "object" }, - "introspection": { - "description": "Enable introspection Default: false", - "default": false, - "type": "boolean" + { + "additionalProperties": false, + "description": "At least one sub-conditions must be true.", + "properties": { + "any": { + "items": { + "$ref": "#/definitions/Condition_for_SubgraphSelector", + "description": "#/definitions/Condition_for_SubgraphSelector" + }, + "type": "array" + } + }, + "required": [ + "any" + ], + "type": "object" }, - "listen": { - "description": "The socket address and port to listen on Defaults to 127.0.0.1:4000", - "default": "127.0.0.1:4000", - "anyOf": [ - { - "description": "Socket address.", - "type": "string" - }, - { - "description": "Unix socket.", - "type": "string" + { + "additionalProperties": false, + "description": "The sub-condition must not be true", + "properties": { + "not": { + "$ref": "#/definitions/Condition_for_SubgraphSelector", + "description": "#/definitions/Condition_for_SubgraphSelector" } - ] + }, + "required": [ + "not" + ], + "type": "object" }, - "path": { - "description": "The HTTP path on which GraphQL requests will be served. default: \"/\"", - "default": "/", + { + "description": "Static true condition", + "enum": [ + "true" + ], "type": "string" }, - "query_planning": { - "description": "Query planning options", - "default": { - "cache": { - "in_memory": { - "limit": 512 + { + "description": "Static false condition", + "enum": [ + "false" + ], + "type": "string" + } + ] + }, + "Condition_for_SupergraphSelector": { + "oneOf": [ + { + "additionalProperties": false, + "description": "A condition to check a selection against a value.", + "properties": { + "eq": { + "items": { + "$ref": "#/definitions/SelectorOrValue_for_SupergraphSelector", + "description": "#/definitions/SelectorOrValue_for_SupergraphSelector" }, - "redis": null - }, - "warmed_up_queries": null, - "experimental_plans_limit": null, - "experimental_paths_limit": null, - "experimental_reuse_query_plans": false, - "experimental_parallelism": 1 + "maxItems": 2, + "minItems": 2, + "type": "array" + } }, - "type": "object", + "required": [ + "eq" + ], + "type": "object" + }, + { + "additionalProperties": false, + "description": "The first selection must be greater than the second selection.", "properties": { - "cache": { - "description": "Cache configuration", - "default": { - "in_memory": { - "limit": 512 - }, - "redis": null + "gt": { + "items": { + "$ref": "#/definitions/SelectorOrValue_for_SupergraphSelector", + "description": "#/definitions/SelectorOrValue_for_SupergraphSelector" }, - "type": "object", - "properties": { - "in_memory": { - "description": "Configures the in memory cache (always active)", - "default": { - "limit": 512 - }, - "type": "object", - "required": [ - "limit" - ], - "properties": { - "limit": { - "description": "Number of entries in the Least Recently Used cache", - "type": "integer", - "format": "uint", - "minimum": 1.0 - } - }, - "additionalProperties": false - }, - "redis": { - "description": "Configures and activates the Redis cache", - "type": "object", - "required": [ - "urls" - ], - "properties": { - "namespace": { - "description": "namespace used to prefix Redis keys", - "type": "string", - "nullable": true - }, - "password": { - "description": "Redis password if not provided in the URLs. This field takes precedence over the password in the URL", - "type": "string", - "nullable": true - }, - "required_to_start": { - "description": "Prevents the router from starting if it cannot connect to Redis", - "default": false, - "type": "boolean" - }, - "reset_ttl": { - "description": "When a TTL is set on a key, reset it when reading the data from that key", - "default": true, - "type": "boolean" - }, - "timeout": { - "description": "Redis request timeout (default: 2ms)", - "type": "string", - "nullable": true - }, - "tls": { - "description": "TLS client configuration", - "type": "object", - "properties": { - "certificate_authorities": { - "description": "list of certificate authorities in PEM format", - "type": "string", - "nullable": true - }, - "client_authentication": { - "description": "client certificate authentication", - "type": "object", - "required": [ - "certificate_chain", - "key" - ], - "properties": { - "certificate_chain": { - "description": "list of certificates in PEM format", - "writeOnly": true, - "type": "string" - }, - "key": { - "description": "key in PEM format", - "writeOnly": true, - "type": "string" - } - }, - "additionalProperties": false, - "nullable": true - } - }, - "additionalProperties": false, - "nullable": true - }, - "ttl": { - "description": "TTL for entries", - "default": { - "secs": 2592000, - "nanos": 0 - }, - "type": "string", - "nullable": true - }, - "urls": { - "description": "List of URLs to the Redis cluster", - "type": "array", - "items": { - "type": "string", - "format": "uri" - } - }, - "username": { - "description": "Redis username if not provided in the URLs. This field takes precedence over the username in the URL", - "type": "string", - "nullable": true - } - }, - "additionalProperties": false, - "nullable": true - } + "maxItems": 2, + "minItems": 2, + "type": "array" + } + }, + "required": [ + "gt" + ], + "type": "object" + }, + { + "additionalProperties": false, + "description": "The first selection must be less than the second selection.", + "properties": { + "lt": { + "items": { + "$ref": "#/definitions/SelectorOrValue_for_SupergraphSelector", + "description": "#/definitions/SelectorOrValue_for_SupergraphSelector" }, - "additionalProperties": false - }, - "experimental_parallelism": { - "description": "Set the size of a pool of workers to enable query planning parallelism. Default: 1.", - "default": 1, - "anyOf": [ - { - "type": "string", - "enum": [ - "auto" - ] - }, - { - "type": "integer", - "format": "uint", - "minimum": 1.0 - } - ] - }, - "experimental_paths_limit": { - "description": "Before creating query plans, for each path of fields in the query we compute all the possible options to traverse that path via the subgraphs. Multiple options can arise because fields in the path can be provided by multiple subgraphs, and abstract types (i.e. unions and interfaces) returned by fields sometimes require the query planner to traverse through each constituent object type. The number of options generated in this computation can grow large if the schema or query are sufficiently complex, and that will increase the time spent planning.\n\nThis config allows specifying a per-path limit to the number of options considered. If any path's options exceeds this limit, query planning will abort and the operation will fail.\n\nThe default value is None, which specifies no limit.", - "type": "integer", - "format": "uint32", - "minimum": 0.0, - "nullable": true - }, - "experimental_plans_limit": { - "description": "Sets a limit to the number of generated query plans. The planning process generates many different query plans as it explores the graph, and the list can grow large. By using this limit, we prevent that growth and still get a valid query plan, but it may not be the optimal one.\n\nThe default limit is set to 10000, but it may change in the future", - "type": "integer", - "format": "uint32", - "minimum": 0.0, - "nullable": true - }, - "experimental_reuse_query_plans": { - "description": "If cache warm up is configured, this will allow the router to keep a query plan created with the old schema, if it determines that the schema update does not affect the corresponding query", - "default": false, - "type": "boolean" - }, - "warmed_up_queries": { - "description": "Warms up the cache on reloads by running the query plan over a list of the most used queries (from the in memory cache) Configures the number of queries warmed up. Defaults to 1/3 of the in memory cache", - "type": "integer", - "format": "uint", - "minimum": 0.0, - "nullable": true + "maxItems": 2, + "minItems": 2, + "type": "array" } }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - "telemetry": { - "description": "Telemetry configuration", - "type": "object", - "properties": { - "apollo": { - "description": "Apollo reporting configuration", - "type": "object", + "required": [ + "lt" + ], + "type": "object" + }, + { + "additionalProperties": false, + "description": "A condition to check a selection against a selector.", "properties": { - "batch_processor": { - "description": "Configuration for batch processing.", - "type": "object", - "properties": { - "max_concurrent_exports": { - "description": "Maximum number of concurrent exports\n\nLimits the number of spawned tasks for exports and thus memory consumed by an exporter. A value of 1 will cause exports to be performed synchronously on the BatchSpanProcessor task. The default is 1.", - "default": 1, - "type": "integer", - "format": "uint", - "minimum": 0.0 - }, - "max_export_batch_size": { - "description": "The maximum number of spans to process in a single batch. If there are more than one batch worth of spans then it processes multiple batches of spans one batch after the other without any delay. The default value is 512.", - "default": 512, - "type": "integer", - "format": "uint", - "minimum": 0.0 - }, - "max_export_timeout": { - "description": "The maximum duration to export a batch of data. The default value is 30 seconds.", - "default": { - "secs": 30, - "nanos": 0 - }, - "type": "string" - }, - "max_queue_size": { - "description": "The maximum queue size to buffer spans for delayed processing. If the queue gets full it drops the spans. The default value of is 2048.", - "default": 2048, - "type": "integer", - "format": "uint", - "minimum": 0.0 - }, - "scheduled_delay": { - "description": "The delay interval in milliseconds between two consecutive processing of batches. The default value is 5 seconds.", - "default": { - "secs": 5, - "nanos": 0 - }, - "type": "string" - } - } - }, - "buffer_size": { - "description": "The buffer size for sending traces to Apollo. Increase this if you are experiencing lost traces.", - "default": 10000, - "type": "integer", - "format": "uint", - "minimum": 1.0 - }, - "client_name_header": { - "description": "The name of the header to extract from requests when populating 'client nane' for traces and metrics in Apollo Studio.", - "default": "apollographql-client-name", - "type": "string", - "nullable": true - }, - "client_version_header": { - "description": "The name of the header to extract from requests when populating 'client version' for traces and metrics in Apollo Studio.", - "default": "apollographql-client-version", - "type": "string", - "nullable": true - }, - "endpoint": { - "description": "The Apollo Studio endpoint for exporting traces and metrics.", - "default": "https://usage-reporting.api.apollographql.com/api/ingress/traces", - "type": "string" - }, - "errors": { - "description": "Configure the way errors are transmitted to Apollo Studio", - "type": "object", - "properties": { - "subgraph": { - "description": "Handling of errors coming from subgraph", - "type": "object", - "properties": { - "all": { - "description": "Handling of errors coming from all subgraphs", - "type": "object", - "properties": { - "redact": { - "description": "Redact subgraph errors to Apollo Studio", - "default": true, - "type": "boolean" - }, - "send": { - "description": "Send subgraph errors to Apollo Studio", - "default": true, - "type": "boolean" - } - }, - "additionalProperties": false - }, - "subgraphs": { - "description": "Handling of errors coming from specified subgraphs", - "type": "object", - "additionalProperties": { - "type": "object", - "properties": { - "redact": { - "description": "Redact subgraph errors to Apollo Studio", - "default": true, - "type": "boolean" - }, - "send": { - "description": "Send subgraph errors to Apollo Studio", - "default": true, - "type": "boolean" - } - }, - "additionalProperties": false - } - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - "experimental_otlp_endpoint": { - "description": "The Apollo Studio endpoint for exporting traces and metrics.", - "default": "https://usage-reporting.api.apollographql.com/", - "type": "string" - }, - "field_level_instrumentation_sampler": { - "description": "Field level instrumentation for subgraphs via ftv1. ftv1 tracing can cause performance issues as it is transmitted in band with subgraph responses.", - "anyOf": [ - { - "description": "Sample a given fraction. Fractions >= 1 will always sample.", - "type": "number", - "format": "double" - }, - { - "oneOf": [ - { - "description": "Always sample", - "type": "string", - "enum": [ - "always_on" - ] - }, - { - "description": "Never sample", - "type": "string", - "enum": [ - "always_off" - ] - } - ] - } - ] - }, - "send_headers": { - "description": "To configure which request header names and values are included in trace data that's sent to Apollo Studio.", - "oneOf": [ - { - "description": "Don't send any headers", - "type": "string", - "enum": [ - "none" - ] - }, - { - "description": "Send all headers", - "type": "string", - "enum": [ - "all" - ] - }, - { - "description": "Send only the headers specified", - "type": "object", - "required": [ - "only" - ], - "properties": { - "only": { - "description": "Send only the headers specified", - "type": "array", - "items": { - "type": "string" - } - } - }, - "additionalProperties": false - }, - { - "description": "Send all headers except those specified", - "type": "object", - "required": [ - "except" - ], - "properties": { - "except": { - "description": "Send all headers except those specified", - "type": "array", - "items": { - "type": "string" - } - } - }, - "additionalProperties": false - } - ] - }, - "send_variable_values": { - "description": "To configure which GraphQL variable values are included in trace data that's sent to Apollo Studio", - "oneOf": [ - { - "description": "Dont send any variables", - "type": "string", - "enum": [ - "none" - ] - }, - { - "description": "Send all variables", - "type": "string", - "enum": [ - "all" - ] - }, - { - "description": "Send only the variables specified", - "type": "object", - "required": [ - "only" - ], - "properties": { - "only": { - "description": "Send only the variables specified", - "type": "array", - "items": { - "type": "string" - } - } - }, - "additionalProperties": false - }, - { - "description": "Send all variables except those specified", - "type": "object", - "required": [ - "except" - ], - "properties": { - "except": { - "description": "Send all variables except those specified", - "type": "array", - "items": { - "type": "string" - } - } - }, - "additionalProperties": false - } - ] + "exists": { + "$ref": "#/definitions/SupergraphSelector", + "description": "#/definitions/SupergraphSelector" } }, - "additionalProperties": false + "required": [ + "exists" + ], + "type": "object" }, - "exporters": { - "description": "Instrumentation configuration", - "type": "object", + { + "additionalProperties": false, + "description": "All sub-conditions must be true.", "properties": { - "logging": { - "description": "Logging configuration", - "type": "object", - "properties": { - "common": { - "description": "Common configuration", - "type": "object", - "properties": { - "resource": { - "description": "The Open Telemetry resource", - "default": {}, - "type": "object", - "additionalProperties": { - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - } - }, - "service_name": { - "description": "Set a service.name resource in your metrics", - "type": "string", - "nullable": true - }, - "service_namespace": { - "description": "Set a service.namespace attribute in your metrics", - "type": "string", - "nullable": true - } - }, - "additionalProperties": false - }, - "experimental_when_header": { - "description": "Log configuration to log request and response for subgraphs and supergraph Note that this will be removed when events are implemented.", - "type": "array", - "items": { - "anyOf": [ - { - "description": "Match header value given a regex to display logs", - "type": "object", - "required": [ - "match", - "name" - ], - "properties": { - "body": { - "description": "Display request/response body (default: false)", - "default": false, - "type": "boolean" - }, - "headers": { - "description": "Display request/response headers (default: false)", - "default": false, - "type": "boolean" - }, - "match": { - "description": "Regex to match the header value", - "type": "string" - }, - "name": { - "description": "Header name", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Match header value given a value to display logs", - "type": "object", - "required": [ - "name", - "value" - ], - "properties": { - "body": { - "description": "Display request/response body (default: false)", - "default": false, - "type": "boolean" - }, - "headers": { - "description": "Display request/response headers (default: false)", - "default": false, - "type": "boolean" - }, - "name": { - "description": "Header name", - "type": "string" - }, - "value": { - "description": "Header value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - }, - "stdout": { - "description": "Settings for logging to stdout.", - "type": "object", - "properties": { - "enabled": { - "description": "Set to true to log to stdout.", - "default": true, - "type": "boolean" - }, - "format": { - "description": "The format to log to stdout.", - "oneOf": [ - { - "description": "Tracing subscriber https://docs.rs/tracing-subscriber/latest/tracing_subscriber/fmt/format/struct.Json.html", - "type": "object", - "required": [ - "json" - ], - "properties": { - "json": { - "type": "object", - "properties": { - "display_current_span": { - "description": "Include the current span in this log event.", - "default": false, - "type": "boolean" - }, - "display_filename": { - "description": "Include the filename with the log event.", - "default": false, - "type": "boolean" - }, - "display_level": { - "description": "Include the level with the log event. (default: true)", - "default": true, - "type": "boolean" - }, - "display_line_number": { - "description": "Include the line number with the log event.", - "default": false, - "type": "boolean" - }, - "display_resource": { - "description": "Include the resource with the log event. (default: true)", - "default": true, - "type": "boolean" - }, - "display_span_id": { - "description": "Include the span id (if any) with the log event. (default: true)", - "default": true, - "type": "boolean" - }, - "display_span_list": { - "description": "Include all of the containing span information with the log event. (default: true)", - "default": true, - "type": "boolean" - }, - "display_target": { - "description": "Include the target with the log event. (default: true)", - "default": true, - "type": "boolean" - }, - "display_thread_id": { - "description": "Include the thread_id with the log event.", - "default": false, - "type": "boolean" - }, - "display_thread_name": { - "description": "Include the thread_name with the log event.", - "default": false, - "type": "boolean" - }, - "display_timestamp": { - "description": "Include the timestamp with the log event. (default: true)", - "default": true, - "type": "boolean" - }, - "display_trace_id": { - "description": "Include the trace id (if any) with the log event. (default: true)", - "default": true, - "type": "boolean" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Tracing subscriber https://docs.rs/tracing-subscriber/latest/tracing_subscriber/fmt/format/struct.Json.html", - "type": "string", - "enum": [ - "json" - ] - }, - { - "description": "Tracing subscriber https://docs.rs/tracing-subscriber/latest/tracing_subscriber/fmt/format/struct.Full.html", - "type": "object", - "required": [ - "text" - ], - "properties": { - "text": { - "type": "object", - "properties": { - "ansi_escape_codes": { - "description": "Process ansi escapes (default: true)", - "default": true, - "type": "boolean" - }, - "display_current_span": { - "description": "Include the current span in this log event. (default: true)", - "default": true, - "type": "boolean" - }, - "display_filename": { - "description": "Include the filename with the log event.", - "default": false, - "type": "boolean" - }, - "display_level": { - "description": "Include the level with the log event. (default: true)", - "default": true, - "type": "boolean" - }, - "display_line_number": { - "description": "Include the line number with the log event.", - "default": false, - "type": "boolean" - }, - "display_resource": { - "description": "Include the resource with the log event.", - "default": false, - "type": "boolean" - }, - "display_service_name": { - "description": "Include the service name with the log event.", - "default": false, - "type": "boolean" - }, - "display_service_namespace": { - "description": "Include the service namespace with the log event.", - "default": false, - "type": "boolean" - }, - "display_span_id": { - "description": "Include the span id (if any) with the log event. (default: false)", - "default": false, - "type": "boolean" - }, - "display_span_list": { - "description": "Include all of the containing span information with the log event. (default: true)", - "default": true, - "type": "boolean" - }, - "display_target": { - "description": "Include the target with the log event.", - "default": false, - "type": "boolean" - }, - "display_thread_id": { - "description": "Include the thread_id with the log event.", - "default": false, - "type": "boolean" - }, - "display_thread_name": { - "description": "Include the thread_name with the log event.", - "default": false, - "type": "boolean" - }, - "display_timestamp": { - "description": "Include the timestamp with the log event. (default: true)", - "default": true, - "type": "boolean" - }, - "display_trace_id": { - "description": "Include the trace id (if any) with the log event. (default: false)", - "default": false, - "type": "boolean" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Tracing subscriber https://docs.rs/tracing-subscriber/latest/tracing_subscriber/fmt/format/struct.Full.html", - "type": "string", - "enum": [ - "text" - ] - } - ] - }, - "rate_limit": { - "description": "Log rate limiting. The limit is set per type of log message", - "type": "object", - "properties": { - "capacity": { - "description": "Number of log lines allowed in interval per message", - "default": 1, - "type": "integer", - "format": "uint32", - "minimum": 0.0 - }, - "enabled": { - "description": "Set to true to limit the rate of log messages", - "default": false, - "type": "boolean" - }, - "interval": { - "description": "Interval for rate limiting", - "default": { - "secs": 1, - "nanos": 0 - }, - "type": "string" - } - }, - "additionalProperties": false - }, - "tty_format": { - "description": "The format to log to stdout when you're running on an interactive terminal. When configured it will automatically use this `tty_format`` instead of the original `format` when an interactive terminal is detected", - "oneOf": [ - { - "description": "Tracing subscriber https://docs.rs/tracing-subscriber/latest/tracing_subscriber/fmt/format/struct.Json.html", - "type": "object", - "required": [ - "json" - ], - "properties": { - "json": { - "type": "object", - "properties": { - "display_current_span": { - "description": "Include the current span in this log event.", - "default": false, - "type": "boolean" - }, - "display_filename": { - "description": "Include the filename with the log event.", - "default": false, - "type": "boolean" - }, - "display_level": { - "description": "Include the level with the log event. (default: true)", - "default": true, - "type": "boolean" - }, - "display_line_number": { - "description": "Include the line number with the log event.", - "default": false, - "type": "boolean" - }, - "display_resource": { - "description": "Include the resource with the log event. (default: true)", - "default": true, - "type": "boolean" - }, - "display_span_id": { - "description": "Include the span id (if any) with the log event. (default: true)", - "default": true, - "type": "boolean" - }, - "display_span_list": { - "description": "Include all of the containing span information with the log event. (default: true)", - "default": true, - "type": "boolean" - }, - "display_target": { - "description": "Include the target with the log event. (default: true)", - "default": true, - "type": "boolean" - }, - "display_thread_id": { - "description": "Include the thread_id with the log event.", - "default": false, - "type": "boolean" - }, - "display_thread_name": { - "description": "Include the thread_name with the log event.", - "default": false, - "type": "boolean" - }, - "display_timestamp": { - "description": "Include the timestamp with the log event. (default: true)", - "default": true, - "type": "boolean" - }, - "display_trace_id": { - "description": "Include the trace id (if any) with the log event. (default: true)", - "default": true, - "type": "boolean" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Tracing subscriber https://docs.rs/tracing-subscriber/latest/tracing_subscriber/fmt/format/struct.Json.html", - "type": "string", - "enum": [ - "json" - ] - }, - { - "description": "Tracing subscriber https://docs.rs/tracing-subscriber/latest/tracing_subscriber/fmt/format/struct.Full.html", - "type": "object", - "required": [ - "text" - ], - "properties": { - "text": { - "type": "object", - "properties": { - "ansi_escape_codes": { - "description": "Process ansi escapes (default: true)", - "default": true, - "type": "boolean" - }, - "display_current_span": { - "description": "Include the current span in this log event. (default: true)", - "default": true, - "type": "boolean" - }, - "display_filename": { - "description": "Include the filename with the log event.", - "default": false, - "type": "boolean" - }, - "display_level": { - "description": "Include the level with the log event. (default: true)", - "default": true, - "type": "boolean" - }, - "display_line_number": { - "description": "Include the line number with the log event.", - "default": false, - "type": "boolean" - }, - "display_resource": { - "description": "Include the resource with the log event.", - "default": false, - "type": "boolean" - }, - "display_service_name": { - "description": "Include the service name with the log event.", - "default": false, - "type": "boolean" - }, - "display_service_namespace": { - "description": "Include the service namespace with the log event.", - "default": false, - "type": "boolean" - }, - "display_span_id": { - "description": "Include the span id (if any) with the log event. (default: false)", - "default": false, - "type": "boolean" - }, - "display_span_list": { - "description": "Include all of the containing span information with the log event. (default: true)", - "default": true, - "type": "boolean" - }, - "display_target": { - "description": "Include the target with the log event.", - "default": false, - "type": "boolean" - }, - "display_thread_id": { - "description": "Include the thread_id with the log event.", - "default": false, - "type": "boolean" - }, - "display_thread_name": { - "description": "Include the thread_name with the log event.", - "default": false, - "type": "boolean" - }, - "display_timestamp": { - "description": "Include the timestamp with the log event. (default: true)", - "default": true, - "type": "boolean" - }, - "display_trace_id": { - "description": "Include the trace id (if any) with the log event. (default: false)", - "default": false, - "type": "boolean" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Tracing subscriber https://docs.rs/tracing-subscriber/latest/tracing_subscriber/fmt/format/struct.Full.html", - "type": "string", - "enum": [ - "text" - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - "metrics": { - "description": "Metrics configuration", - "type": "object", - "properties": { - "common": { - "description": "Common metrics configuration across all exporters", - "type": "object", - "properties": { - "attributes": { - "description": "Configuration to add custom labels/attributes to metrics", - "type": "object", - "properties": { - "subgraph": { - "description": "Configuration to forward header values or body values from subgraph request/response in metric attributes/labels", - "type": "object", - "properties": { - "all": { - "description": "Attributes for all subgraphs", - "type": "object", - "properties": { - "context": { - "description": "Configuration to forward values from the context to custom attributes/labels in metrics", - "type": "array", - "items": { - "description": "Configuration to forward context values in metric attributes/labels", - "type": "object", - "required": [ - "named" - ], - "properties": { - "default": { - "description": "The optional default value", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "named": { - "description": "The name of the value in the context", - "type": "string" - }, - "rename": { - "description": "The optional output name", - "type": "string", - "nullable": true - } - }, - "additionalProperties": false - } - }, - "errors": { - "description": "Configuration to forward values from the error to custom attributes/labels in metrics", - "type": "object", - "properties": { - "extensions": { - "description": "Forward extensions values as custom attributes/labels in metrics", - "type": "array", - "items": { - "description": "Configuration to forward body values in metric attributes/labels", - "type": "object", - "required": [ - "name", - "path" - ], - "properties": { - "default": { - "description": "The optional default value", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "name": { - "description": "The name of the attribute", - "type": "string" - }, - "path": { - "description": "The path in the body", - "type": "string" - } - }, - "additionalProperties": false - } - }, - "include_messages": { - "description": "Will include the error message in a \"message\" attribute", - "type": "boolean", - "nullable": true - } - }, - "additionalProperties": false - }, - "request": { - "description": "Configuration to forward headers or body values from the request to custom attributes/labels in metrics", - "type": "object", - "properties": { - "body": { - "description": "Forward body values as custom attributes/labels in metrics", - "type": "array", - "items": { - "description": "Configuration to forward body values in metric attributes/labels", - "type": "object", - "required": [ - "name", - "path" - ], - "properties": { - "default": { - "description": "The optional default value", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "name": { - "description": "The name of the attribute", - "type": "string" - }, - "path": { - "description": "The path in the body", - "type": "string" - } - }, - "additionalProperties": false - } - }, - "header": { - "description": "Forward header values as custom attributes/labels in metrics", - "type": "array", - "items": { - "description": "Configuration to forward header values in metric labels", - "anyOf": [ - { - "description": "Match via header name", - "type": "object", - "required": [ - "named" - ], - "properties": { - "default": { - "description": "The optional default value", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "named": { - "description": "The name of the header", - "type": "string" - }, - "rename": { - "description": "The optional output name", - "type": "string", - "nullable": true - } - }, - "additionalProperties": false - }, - { - "description": "Match via rgex", - "type": "object", - "required": [ - "matching" - ], - "properties": { - "matching": { - "description": "Using a regex on the header name", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - } - }, - "additionalProperties": false - }, - "response": { - "description": "Configuration to forward headers or body values from the response to custom attributes/labels in metrics", - "type": "object", - "properties": { - "body": { - "description": "Forward body values as custom attributes/labels in metrics", - "type": "array", - "items": { - "description": "Configuration to forward body values in metric attributes/labels", - "type": "object", - "required": [ - "name", - "path" - ], - "properties": { - "default": { - "description": "The optional default value", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "name": { - "description": "The name of the attribute", - "type": "string" - }, - "path": { - "description": "The path in the body", - "type": "string" - } - }, - "additionalProperties": false - } - }, - "header": { - "description": "Forward header values as custom attributes/labels in metrics", - "type": "array", - "items": { - "description": "Configuration to forward header values in metric labels", - "anyOf": [ - { - "description": "Match via header name", - "type": "object", - "required": [ - "named" - ], - "properties": { - "default": { - "description": "The optional default value", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "named": { - "description": "The name of the header", - "type": "string" - }, - "rename": { - "description": "The optional output name", - "type": "string", - "nullable": true - } - }, - "additionalProperties": false - }, - { - "description": "Match via rgex", - "type": "object", - "required": [ - "matching" - ], - "properties": { - "matching": { - "description": "Using a regex on the header name", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - } - }, - "additionalProperties": false - }, - "static": { - "description": "Configuration to insert custom attributes/labels in metrics", - "type": "array", - "items": { - "description": "Configuration to insert custom attributes/labels in metrics", - "type": "object", - "required": [ - "name", - "value" - ], - "properties": { - "name": { - "description": "The name of the attribute to insert", - "type": "string" - }, - "value": { - "description": "The value of the attribute to insert", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - } - }, - "additionalProperties": false - } - } - }, - "additionalProperties": false - }, - "subgraphs": { - "description": "Attributes per subgraph", - "type": "object", - "additionalProperties": { - "description": "Configuration to add custom attributes/labels on metrics to subgraphs", - "type": "object", - "properties": { - "context": { - "description": "Configuration to forward values from the context to custom attributes/labels in metrics", - "type": "array", - "items": { - "description": "Configuration to forward context values in metric attributes/labels", - "type": "object", - "required": [ - "named" - ], - "properties": { - "default": { - "description": "The optional default value", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "named": { - "description": "The name of the value in the context", - "type": "string" - }, - "rename": { - "description": "The optional output name", - "type": "string", - "nullable": true - } - }, - "additionalProperties": false - } - }, - "errors": { - "description": "Configuration to forward values from the error to custom attributes/labels in metrics", - "type": "object", - "properties": { - "extensions": { - "description": "Forward extensions values as custom attributes/labels in metrics", - "type": "array", - "items": { - "description": "Configuration to forward body values in metric attributes/labels", - "type": "object", - "required": [ - "name", - "path" - ], - "properties": { - "default": { - "description": "The optional default value", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "name": { - "description": "The name of the attribute", - "type": "string" - }, - "path": { - "description": "The path in the body", - "type": "string" - } - }, - "additionalProperties": false - } - }, - "include_messages": { - "description": "Will include the error message in a \"message\" attribute", - "type": "boolean", - "nullable": true - } - }, - "additionalProperties": false - }, - "request": { - "description": "Configuration to forward headers or body values from the request to custom attributes/labels in metrics", - "type": "object", - "properties": { - "body": { - "description": "Forward body values as custom attributes/labels in metrics", - "type": "array", - "items": { - "description": "Configuration to forward body values in metric attributes/labels", - "type": "object", - "required": [ - "name", - "path" - ], - "properties": { - "default": { - "description": "The optional default value", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "name": { - "description": "The name of the attribute", - "type": "string" - }, - "path": { - "description": "The path in the body", - "type": "string" - } - }, - "additionalProperties": false - } - }, - "header": { - "description": "Forward header values as custom attributes/labels in metrics", - "type": "array", - "items": { - "description": "Configuration to forward header values in metric labels", - "anyOf": [ - { - "description": "Match via header name", - "type": "object", - "required": [ - "named" - ], - "properties": { - "default": { - "description": "The optional default value", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "named": { - "description": "The name of the header", - "type": "string" - }, - "rename": { - "description": "The optional output name", - "type": "string", - "nullable": true - } - }, - "additionalProperties": false - }, - { - "description": "Match via rgex", - "type": "object", - "required": [ - "matching" - ], - "properties": { - "matching": { - "description": "Using a regex on the header name", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - } - }, - "additionalProperties": false - }, - "response": { - "description": "Configuration to forward headers or body values from the response to custom attributes/labels in metrics", - "type": "object", - "properties": { - "body": { - "description": "Forward body values as custom attributes/labels in metrics", - "type": "array", - "items": { - "description": "Configuration to forward body values in metric attributes/labels", - "type": "object", - "required": [ - "name", - "path" - ], - "properties": { - "default": { - "description": "The optional default value", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "name": { - "description": "The name of the attribute", - "type": "string" - }, - "path": { - "description": "The path in the body", - "type": "string" - } - }, - "additionalProperties": false - } - }, - "header": { - "description": "Forward header values as custom attributes/labels in metrics", - "type": "array", - "items": { - "description": "Configuration to forward header values in metric labels", - "anyOf": [ - { - "description": "Match via header name", - "type": "object", - "required": [ - "named" - ], - "properties": { - "default": { - "description": "The optional default value", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "named": { - "description": "The name of the header", - "type": "string" - }, - "rename": { - "description": "The optional output name", - "type": "string", - "nullable": true - } - }, - "additionalProperties": false - }, - { - "description": "Match via rgex", - "type": "object", - "required": [ - "matching" - ], - "properties": { - "matching": { - "description": "Using a regex on the header name", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - } - }, - "additionalProperties": false - }, - "static": { - "description": "Configuration to insert custom attributes/labels in metrics", - "type": "array", - "items": { - "description": "Configuration to insert custom attributes/labels in metrics", - "type": "object", - "required": [ - "name", - "value" - ], - "properties": { - "name": { - "description": "The name of the attribute to insert", - "type": "string" - }, - "value": { - "description": "The value of the attribute to insert", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - } - }, - "additionalProperties": false - } - } - }, - "additionalProperties": false - } - } - }, - "additionalProperties": false - }, - "supergraph": { - "description": "Configuration to forward header values or body values from router request/response in metric attributes/labels", - "type": "object", - "properties": { - "context": { - "description": "Configuration to forward values from the context to custom attributes/labels in metrics", - "type": "array", - "items": { - "description": "Configuration to forward context values in metric attributes/labels", - "type": "object", - "required": [ - "named" - ], - "properties": { - "default": { - "description": "The optional default value", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "named": { - "description": "The name of the value in the context", - "type": "string" - }, - "rename": { - "description": "The optional output name", - "type": "string", - "nullable": true - } - }, - "additionalProperties": false - } - }, - "errors": { - "description": "Configuration to forward values from the error to custom attributes/labels in metrics", - "type": "object", - "properties": { - "extensions": { - "description": "Forward extensions values as custom attributes/labels in metrics", - "type": "array", - "items": { - "description": "Configuration to forward body values in metric attributes/labels", - "type": "object", - "required": [ - "name", - "path" - ], - "properties": { - "default": { - "description": "The optional default value", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "name": { - "description": "The name of the attribute", - "type": "string" - }, - "path": { - "description": "The path in the body", - "type": "string" - } - }, - "additionalProperties": false - } - }, - "include_messages": { - "description": "Will include the error message in a \"message\" attribute", - "type": "boolean", - "nullable": true - } - }, - "additionalProperties": false - }, - "request": { - "description": "Configuration to forward headers or body values from the request to custom attributes/labels in metrics", - "type": "object", - "properties": { - "body": { - "description": "Forward body values as custom attributes/labels in metrics", - "type": "array", - "items": { - "description": "Configuration to forward body values in metric attributes/labels", - "type": "object", - "required": [ - "name", - "path" - ], - "properties": { - "default": { - "description": "The optional default value", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "name": { - "description": "The name of the attribute", - "type": "string" - }, - "path": { - "description": "The path in the body", - "type": "string" - } - }, - "additionalProperties": false - } - }, - "header": { - "description": "Forward header values as custom attributes/labels in metrics", - "type": "array", - "items": { - "description": "Configuration to forward header values in metric labels", - "anyOf": [ - { - "description": "Match via header name", - "type": "object", - "required": [ - "named" - ], - "properties": { - "default": { - "description": "The optional default value", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "named": { - "description": "The name of the header", - "type": "string" - }, - "rename": { - "description": "The optional output name", - "type": "string", - "nullable": true - } - }, - "additionalProperties": false - }, - { - "description": "Match via rgex", - "type": "object", - "required": [ - "matching" - ], - "properties": { - "matching": { - "description": "Using a regex on the header name", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - } - }, - "additionalProperties": false - }, - "response": { - "description": "Configuration to forward headers or body values from the response to custom attributes/labels in metrics", - "type": "object", - "properties": { - "body": { - "description": "Forward body values as custom attributes/labels in metrics", - "type": "array", - "items": { - "description": "Configuration to forward body values in metric attributes/labels", - "type": "object", - "required": [ - "name", - "path" - ], - "properties": { - "default": { - "description": "The optional default value", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "name": { - "description": "The name of the attribute", - "type": "string" - }, - "path": { - "description": "The path in the body", - "type": "string" - } - }, - "additionalProperties": false - } - }, - "header": { - "description": "Forward header values as custom attributes/labels in metrics", - "type": "array", - "items": { - "description": "Configuration to forward header values in metric labels", - "anyOf": [ - { - "description": "Match via header name", - "type": "object", - "required": [ - "named" - ], - "properties": { - "default": { - "description": "The optional default value", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "named": { - "description": "The name of the header", - "type": "string" - }, - "rename": { - "description": "The optional output name", - "type": "string", - "nullable": true - } - }, - "additionalProperties": false - }, - { - "description": "Match via rgex", - "type": "object", - "required": [ - "matching" - ], - "properties": { - "matching": { - "description": "Using a regex on the header name", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - } - }, - "additionalProperties": false - }, - "static": { - "description": "Configuration to insert custom attributes/labels in metrics", - "type": "array", - "items": { - "description": "Configuration to insert custom attributes/labels in metrics", - "type": "object", - "required": [ - "name", - "value" - ], - "properties": { - "name": { - "description": "The name of the attribute to insert", - "type": "string" - }, - "value": { - "description": "The value of the attribute to insert", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - } - }, - "additionalProperties": false - } - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - "buckets": { - "description": "Custom buckets for all histograms", - "default": [ - 0.001, - 0.005, - 0.015, - 0.05, - 0.1, - 0.2, - 0.3, - 0.4, - 0.5, - 1.0, - 5.0, - 10.0 - ], - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - "resource": { - "description": "The Open Telemetry resource", - "default": {}, - "type": "object", - "additionalProperties": { - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - } - }, - "service_name": { - "description": "Set a service.name resource in your metrics", - "type": "string", - "nullable": true - }, - "service_namespace": { - "description": "Set a service.namespace attribute in your metrics", - "type": "string", - "nullable": true - }, - "views": { - "description": "Views applied on metrics", - "type": "array", - "items": { - "type": "object", - "required": [ - "name" - ], - "properties": { - "aggregation": { - "description": "New aggregation settings to set", - "oneOf": [ - { - "description": "An aggregation that summarizes a set of measurements as an histogram with explicitly defined buckets.", - "type": "object", - "required": [ - "histogram" - ], - "properties": { - "histogram": { - "type": "object", - "required": [ - "buckets" - ], - "properties": { - "buckets": { - "type": "array", - "items": { - "type": "number", - "format": "double" - } - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ], - "nullable": true - }, - "allowed_attribute_keys": { - "description": "An allow-list of attribute keys that will be preserved for the instrument.\n\nAny attribute recorded for the instrument with a key not in this set will be dropped. If the set is empty, all attributes will be dropped, if `None` all attributes will be kept.", - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true, - "nullable": true - }, - "description": { - "description": "New description to set to the instrument", - "type": "string", - "nullable": true - }, - "name": { - "description": "The instrument name you're targeting", - "type": "string" - }, - "unit": { - "description": "New unit to set to the instrument", - "type": "string", - "nullable": true - } - }, - "additionalProperties": false - } - } - }, - "additionalProperties": false - }, - "otlp": { - "description": "Open Telemetry native exporter configuration", - "type": "object", - "required": [ - "enabled" - ], - "properties": { - "batch_processor": { - "description": "Batch processor settings", - "type": "object", - "properties": { - "max_concurrent_exports": { - "description": "Maximum number of concurrent exports\n\nLimits the number of spawned tasks for exports and thus memory consumed by an exporter. A value of 1 will cause exports to be performed synchronously on the BatchSpanProcessor task. The default is 1.", - "default": 1, - "type": "integer", - "format": "uint", - "minimum": 0.0 - }, - "max_export_batch_size": { - "description": "The maximum number of spans to process in a single batch. If there are more than one batch worth of spans then it processes multiple batches of spans one batch after the other without any delay. The default value is 512.", - "default": 512, - "type": "integer", - "format": "uint", - "minimum": 0.0 - }, - "max_export_timeout": { - "description": "The maximum duration to export a batch of data. The default value is 30 seconds.", - "default": { - "secs": 30, - "nanos": 0 - }, - "type": "string" - }, - "max_queue_size": { - "description": "The maximum queue size to buffer spans for delayed processing. If the queue gets full it drops the spans. The default value of is 2048.", - "default": 2048, - "type": "integer", - "format": "uint", - "minimum": 0.0 - }, - "scheduled_delay": { - "description": "The delay interval in milliseconds between two consecutive processing of batches. The default value is 5 seconds.", - "default": { - "secs": 5, - "nanos": 0 - }, - "type": "string" - } - } - }, - "enabled": { - "description": "Enable otlp", - "type": "boolean" - }, - "endpoint": { - "description": "The endpoint to send data to", - "type": "string" - }, - "grpc": { - "description": "gRPC configuration settings", - "default": { - "domain_name": null, - "ca": null, - "cert": null, - "key": null, - "metadata": {} - }, - "type": "object", - "properties": { - "ca": { - "description": "The optional certificate authority (CA) certificate to be used in TLS configuration.", - "type": "string", - "nullable": true - }, - "cert": { - "description": "The optional cert for tls config", - "type": "string", - "nullable": true - }, - "domain_name": { - "description": "The optional domain name for tls config. Note that domain name is will be defaulted to match the endpoint is not explicitly set.", - "type": "string", - "nullable": true - }, - "key": { - "description": "The optional private key file for TLS configuration.", - "type": "string", - "nullable": true - }, - "metadata": { - "description": "gRPC metadata", - "default": {}, - "type": "object", - "additionalProperties": true - } - }, - "additionalProperties": false - }, - "http": { - "description": "HTTP configuration settings", - "default": { - "headers": {} - }, - "type": "object", - "properties": { - "headers": { - "description": "Headers to send on report requests", - "default": {}, - "type": "object", - "additionalProperties": { - "type": "string" - } - } - }, - "additionalProperties": false - }, - "protocol": { - "description": "The protocol to use when sending data", - "default": "grpc", - "type": "string", - "enum": [ - "grpc", - "http" - ] - }, - "temporality": { - "description": "Temporality for export (default: `Cumulative`). Note that when exporting to Datadog agent use `Delta`.", - "default": "cumulative", - "oneOf": [ - { - "description": "Export cumulative metrics.", - "type": "string", - "enum": [ - "cumulative" - ] - }, - { - "description": "Export delta metrics. `Delta` should be used when exporting to DataDog Agent.", - "type": "string", - "enum": [ - "delta" - ] - } - ] - } - }, - "additionalProperties": false - }, - "prometheus": { - "description": "Prometheus exporter configuration", - "type": "object", - "properties": { - "enabled": { - "description": "Set to true to enable", - "default": false, - "type": "boolean" - }, - "listen": { - "description": "The listen address", - "default": "127.0.0.1:9090", - "anyOf": [ - { - "description": "Socket address.", - "type": "string" - }, - { - "description": "Unix socket.", - "type": "string" - } - ] - }, - "path": { - "description": "The path where prometheus will be exposed", - "default": "/metrics", - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - "tracing": { - "description": "Tracing configuration", - "type": "object", - "properties": { - "common": { - "description": "Common configuration", - "type": "object", - "properties": { - "max_attributes_per_event": { - "description": "The maximum attributes per event before discarding", - "default": 128, - "type": "integer", - "format": "uint32", - "minimum": 0.0 - }, - "max_attributes_per_link": { - "description": "The maximum attributes per link before discarding", - "default": 128, - "type": "integer", - "format": "uint32", - "minimum": 0.0 - }, - "max_attributes_per_span": { - "description": "The maximum attributes per span before discarding", - "default": 128, - "type": "integer", - "format": "uint32", - "minimum": 0.0 - }, - "max_events_per_span": { - "description": "The maximum events per span before discarding", - "default": 128, - "type": "integer", - "format": "uint32", - "minimum": 0.0 - }, - "max_links_per_span": { - "description": "The maximum links per span before discarding", - "default": 128, - "type": "integer", - "format": "uint32", - "minimum": 0.0 - }, - "parent_based_sampler": { - "description": "Whether to use parent based sampling", - "default": true, - "type": "boolean" - }, - "resource": { - "description": "The Open Telemetry resource", - "default": {}, - "type": "object", - "additionalProperties": { - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - } - }, - "sampler": { - "description": "The sampler, always_on, always_off or a decimal between 0.0 and 1.0", - "anyOf": [ - { - "description": "Sample a given fraction. Fractions >= 1 will always sample.", - "type": "number", - "format": "double" - }, - { - "oneOf": [ - { - "description": "Always sample", - "type": "string", - "enum": [ - "always_on" - ] - }, - { - "description": "Never sample", - "type": "string", - "enum": [ - "always_off" - ] - } - ] - } - ] - }, - "service_name": { - "description": "The trace service name", - "type": "string", - "nullable": true - }, - "service_namespace": { - "description": "The trace service namespace", - "type": "string", - "nullable": true - } - }, - "additionalProperties": false - }, - "datadog": { - "description": "Datadog exporter configuration", - "type": "object", - "required": [ - "enabled" - ], - "properties": { - "batch_processor": { - "description": "batch processor configuration", - "type": "object", - "properties": { - "max_concurrent_exports": { - "description": "Maximum number of concurrent exports\n\nLimits the number of spawned tasks for exports and thus memory consumed by an exporter. A value of 1 will cause exports to be performed synchronously on the BatchSpanProcessor task. The default is 1.", - "default": 1, - "type": "integer", - "format": "uint", - "minimum": 0.0 - }, - "max_export_batch_size": { - "description": "The maximum number of spans to process in a single batch. If there are more than one batch worth of spans then it processes multiple batches of spans one batch after the other without any delay. The default value is 512.", - "default": 512, - "type": "integer", - "format": "uint", - "minimum": 0.0 - }, - "max_export_timeout": { - "description": "The maximum duration to export a batch of data. The default value is 30 seconds.", - "default": { - "secs": 30, - "nanos": 0 - }, - "type": "string" - }, - "max_queue_size": { - "description": "The maximum queue size to buffer spans for delayed processing. If the queue gets full it drops the spans. The default value of is 2048.", - "default": 2048, - "type": "integer", - "format": "uint", - "minimum": 0.0 - }, - "scheduled_delay": { - "description": "The delay interval in milliseconds between two consecutive processing of batches. The default value is 5 seconds.", - "default": { - "secs": 5, - "nanos": 0 - }, - "type": "string" - } - } - }, - "enable_span_mapping": { - "description": "Enable datadog span mapping for span name and resource name.", - "default": false, - "type": "boolean" - }, - "enabled": { - "description": "Enable datadog", - "type": "boolean" - }, - "endpoint": { - "description": "The endpoint to send to", - "type": "string" - } - }, - "additionalProperties": false - }, - "experimental_response_trace_id": { - "description": "A way to expose trace id in response headers", - "type": "object", - "properties": { - "enabled": { - "description": "Expose the trace_id in response headers", - "default": false, - "type": "boolean" - }, - "format": { - "description": "Format of the trace ID in response headers", - "oneOf": [ - { - "description": "Format the Trace ID as a hexadecimal number\n\n(e.g. Trace ID 16 -> 00000000000000000000000000000010)", - "type": "string", - "enum": [ - "hexadecimal" - ] - }, - { - "description": "Format the Trace ID as a decimal number\n\n(e.g. Trace ID 16 -> 16)", - "type": "string", - "enum": [ - "decimal" - ] - } - ] - }, - "header_name": { - "description": "Choose the header name to expose trace_id (default: apollo-trace-id)", - "type": "string", - "nullable": true - } - }, - "additionalProperties": false - }, - "jaeger": { - "description": "Jaeger exporter configuration", - "anyOf": [ - { - "type": "object", - "required": [ - "enabled" - ], - "properties": { - "agent": { - "description": "Agent configuration", - "type": "object", - "properties": { - "endpoint": { - "description": "The endpoint to send to", - "type": "string" - } - }, - "additionalProperties": false - }, - "batch_processor": { - "description": "Batch processor configuration", - "type": "object", - "properties": { - "max_concurrent_exports": { - "description": "Maximum number of concurrent exports\n\nLimits the number of spawned tasks for exports and thus memory consumed by an exporter. A value of 1 will cause exports to be performed synchronously on the BatchSpanProcessor task. The default is 1.", - "default": 1, - "type": "integer", - "format": "uint", - "minimum": 0.0 - }, - "max_export_batch_size": { - "description": "The maximum number of spans to process in a single batch. If there are more than one batch worth of spans then it processes multiple batches of spans one batch after the other without any delay. The default value is 512.", - "default": 512, - "type": "integer", - "format": "uint", - "minimum": 0.0 - }, - "max_export_timeout": { - "description": "The maximum duration to export a batch of data. The default value is 30 seconds.", - "default": { - "secs": 30, - "nanos": 0 - }, - "type": "string" - }, - "max_queue_size": { - "description": "The maximum queue size to buffer spans for delayed processing. If the queue gets full it drops the spans. The default value of is 2048.", - "default": 2048, - "type": "integer", - "format": "uint", - "minimum": 0.0 - }, - "scheduled_delay": { - "description": "The delay interval in milliseconds between two consecutive processing of batches. The default value is 5 seconds.", - "default": { - "secs": 5, - "nanos": 0 - }, - "type": "string" - } - } - }, - "enabled": { - "description": "Enable Jaeger", - "type": "boolean" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "enabled" - ], - "properties": { - "batch_processor": { - "description": "Batch processor configuration", - "type": "object", - "properties": { - "max_concurrent_exports": { - "description": "Maximum number of concurrent exports\n\nLimits the number of spawned tasks for exports and thus memory consumed by an exporter. A value of 1 will cause exports to be performed synchronously on the BatchSpanProcessor task. The default is 1.", - "default": 1, - "type": "integer", - "format": "uint", - "minimum": 0.0 - }, - "max_export_batch_size": { - "description": "The maximum number of spans to process in a single batch. If there are more than one batch worth of spans then it processes multiple batches of spans one batch after the other without any delay. The default value is 512.", - "default": 512, - "type": "integer", - "format": "uint", - "minimum": 0.0 - }, - "max_export_timeout": { - "description": "The maximum duration to export a batch of data. The default value is 30 seconds.", - "default": { - "secs": 30, - "nanos": 0 - }, - "type": "string" - }, - "max_queue_size": { - "description": "The maximum queue size to buffer spans for delayed processing. If the queue gets full it drops the spans. The default value of is 2048.", - "default": 2048, - "type": "integer", - "format": "uint", - "minimum": 0.0 - }, - "scheduled_delay": { - "description": "The delay interval in milliseconds between two consecutive processing of batches. The default value is 5 seconds.", - "default": { - "secs": 5, - "nanos": 0 - }, - "type": "string" - } - } - }, - "collector": { - "description": "Collector configuration", - "type": "object", - "properties": { - "endpoint": { - "description": "The endpoint to send reports to", - "type": "string" - }, - "password": { - "description": "The optional password", - "type": "string", - "nullable": true - }, - "username": { - "description": "The optional username", - "type": "string", - "nullable": true - } - }, - "additionalProperties": false - }, - "enabled": { - "description": "Enable Jaeger", - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - }, - "otlp": { - "description": "OpenTelemetry native exporter configuration", - "type": "object", - "required": [ - "enabled" - ], - "properties": { - "batch_processor": { - "description": "Batch processor settings", - "type": "object", - "properties": { - "max_concurrent_exports": { - "description": "Maximum number of concurrent exports\n\nLimits the number of spawned tasks for exports and thus memory consumed by an exporter. A value of 1 will cause exports to be performed synchronously on the BatchSpanProcessor task. The default is 1.", - "default": 1, - "type": "integer", - "format": "uint", - "minimum": 0.0 - }, - "max_export_batch_size": { - "description": "The maximum number of spans to process in a single batch. If there are more than one batch worth of spans then it processes multiple batches of spans one batch after the other without any delay. The default value is 512.", - "default": 512, - "type": "integer", - "format": "uint", - "minimum": 0.0 - }, - "max_export_timeout": { - "description": "The maximum duration to export a batch of data. The default value is 30 seconds.", - "default": { - "secs": 30, - "nanos": 0 - }, - "type": "string" - }, - "max_queue_size": { - "description": "The maximum queue size to buffer spans for delayed processing. If the queue gets full it drops the spans. The default value of is 2048.", - "default": 2048, - "type": "integer", - "format": "uint", - "minimum": 0.0 - }, - "scheduled_delay": { - "description": "The delay interval in milliseconds between two consecutive processing of batches. The default value is 5 seconds.", - "default": { - "secs": 5, - "nanos": 0 - }, - "type": "string" - } - } - }, - "enabled": { - "description": "Enable otlp", - "type": "boolean" - }, - "endpoint": { - "description": "The endpoint to send data to", - "type": "string" - }, - "grpc": { - "description": "gRPC configuration settings", - "default": { - "domain_name": null, - "ca": null, - "cert": null, - "key": null, - "metadata": {} - }, - "type": "object", - "properties": { - "ca": { - "description": "The optional certificate authority (CA) certificate to be used in TLS configuration.", - "type": "string", - "nullable": true - }, - "cert": { - "description": "The optional cert for tls config", - "type": "string", - "nullable": true - }, - "domain_name": { - "description": "The optional domain name for tls config. Note that domain name is will be defaulted to match the endpoint is not explicitly set.", - "type": "string", - "nullable": true - }, - "key": { - "description": "The optional private key file for TLS configuration.", - "type": "string", - "nullable": true - }, - "metadata": { - "description": "gRPC metadata", - "default": {}, - "type": "object", - "additionalProperties": true - } - }, - "additionalProperties": false - }, - "http": { - "description": "HTTP configuration settings", - "default": { - "headers": {} - }, - "type": "object", - "properties": { - "headers": { - "description": "Headers to send on report requests", - "default": {}, - "type": "object", - "additionalProperties": { - "type": "string" - } - } - }, - "additionalProperties": false - }, - "protocol": { - "description": "The protocol to use when sending data", - "default": "grpc", - "type": "string", - "enum": [ - "grpc", - "http" - ] - }, - "temporality": { - "description": "Temporality for export (default: `Cumulative`). Note that when exporting to Datadog agent use `Delta`.", - "default": "cumulative", - "oneOf": [ - { - "description": "Export cumulative metrics.", - "type": "string", - "enum": [ - "cumulative" - ] - }, - { - "description": "Export delta metrics. `Delta` should be used when exporting to DataDog Agent.", - "type": "string", - "enum": [ - "delta" - ] - } - ] - } - }, - "additionalProperties": false - }, - "propagation": { - "description": "Propagation configuration", - "type": "object", - "properties": { - "aws_xray": { - "description": "Propagate AWS X-Ray", - "default": false, - "type": "boolean" - }, - "baggage": { - "description": "Propagate baggage https://www.w3.org/TR/baggage/", - "default": false, - "type": "boolean" - }, - "datadog": { - "description": "Propagate Datadog", - "default": false, - "type": "boolean" - }, - "jaeger": { - "description": "Propagate Jaeger", - "default": false, - "type": "boolean" - }, - "request": { - "description": "Select a custom request header to set your own trace_id (header value must be convertible from hexadecimal to set a correct trace_id)", - "type": "object", - "required": [ - "header_name" - ], - "properties": { - "header_name": { - "description": "Choose the header name to expose trace_id (default: apollo-trace-id)", - "type": "string" - } - }, - "additionalProperties": false - }, - "trace_context": { - "description": "Propagate trace context https://www.w3.org/TR/trace-context/", - "default": false, - "type": "boolean" - }, - "zipkin": { - "description": "Propagate Zipkin", - "default": false, - "type": "boolean" - } - }, - "additionalProperties": false - }, - "zipkin": { - "description": "Zipkin exporter configuration", - "type": "object", - "required": [ - "enabled" - ], - "properties": { - "batch_processor": { - "description": "Batch processor configuration", - "type": "object", - "properties": { - "max_concurrent_exports": { - "description": "Maximum number of concurrent exports\n\nLimits the number of spawned tasks for exports and thus memory consumed by an exporter. A value of 1 will cause exports to be performed synchronously on the BatchSpanProcessor task. The default is 1.", - "default": 1, - "type": "integer", - "format": "uint", - "minimum": 0.0 - }, - "max_export_batch_size": { - "description": "The maximum number of spans to process in a single batch. If there are more than one batch worth of spans then it processes multiple batches of spans one batch after the other without any delay. The default value is 512.", - "default": 512, - "type": "integer", - "format": "uint", - "minimum": 0.0 - }, - "max_export_timeout": { - "description": "The maximum duration to export a batch of data. The default value is 30 seconds.", - "default": { - "secs": 30, - "nanos": 0 - }, - "type": "string" - }, - "max_queue_size": { - "description": "The maximum queue size to buffer spans for delayed processing. If the queue gets full it drops the spans. The default value of is 2048.", - "default": 2048, - "type": "integer", - "format": "uint", - "minimum": 0.0 - }, - "scheduled_delay": { - "description": "The delay interval in milliseconds between two consecutive processing of batches. The default value is 5 seconds.", - "default": { - "secs": 5, - "nanos": 0 - }, - "type": "string" - } - } - }, - "enabled": { - "description": "Enable zipkin", - "type": "boolean" - }, - "endpoint": { - "description": "The endpoint to send to", - "type": "string" - } - }, - "additionalProperties": false - } + "all": { + "items": { + "$ref": "#/definitions/Condition_for_SupergraphSelector", + "description": "#/definitions/Condition_for_SupergraphSelector" }, - "additionalProperties": false + "type": "array" } }, - "additionalProperties": false + "required": [ + "all" + ], + "type": "object" }, - "instrumentation": { - "description": "Instrumentation configuration", - "type": "object", + { + "additionalProperties": false, + "description": "At least one sub-conditions must be true.", "properties": { - "events": { - "description": "Event configuration", - "type": "object", - "properties": { - "router": { - "description": "Router service events", - "type": "object", - "properties": { - "error": { - "description": "Log the router error", - "type": "string", - "enum": [ - "info", - "warn", - "error", - "off" - ] - }, - "request": { - "description": "Log the router request", - "type": "string", - "enum": [ - "info", - "warn", - "error", - "off" - ] - }, - "response": { - "description": "Log the router response", - "type": "string", - "enum": [ - "info", - "warn", - "error", - "off" - ] - } - }, - "additionalProperties": { - "description": "An event that can be logged as part of a trace. The event has an implicit `type` attribute that matches the name of the event in the yaml and a message that can be used to provide additional information.", - "type": "object", - "required": [ - "level", - "message", - "on" - ], - "properties": { - "attributes": { - "description": "The event attributes.", - "type": "object", - "properties": { - "baggage": { - "description": "All key values from trace baggage.", - "type": "boolean", - "nullable": true - }, - "dd.trace_id": { - "description": "The datadog trace ID. This can be output in logs and used to correlate traces in Datadog.", - "type": "boolean", - "nullable": true - }, - "error.type": { - "description": "Describes a class of error the operation ended with. Examples: * timeout * name_resolution_error * 500 Requirement level: Conditionally Required: If request has ended with an error.", - "type": "boolean", - "nullable": true - }, - "http.request.body.size": { - "description": "The size of the request payload body in bytes. This is the number of bytes transferred excluding headers and is often, but not always, present as the Content-Length header. For requests using transport encoding, this should be the compressed size. Examples: * 3495 Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "http.request.method": { - "description": "HTTP request method. Examples: * GET * POST * HEAD Requirement level: Required", - "type": "boolean", - "nullable": true - }, - "http.response.body.size": { - "description": "The size of the response payload body in bytes. This is the number of bytes transferred excluding headers and is often, but not always, present as the Content-Length header. For requests using transport encoding, this should be the compressed size. Examples: * 3495 Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "http.response.status_code": { - "description": "HTTP response status code. Examples: * 200 Requirement level: Conditionally Required: If and only if one was received/sent.", - "type": "boolean", - "nullable": true - }, - "http.route": { - "description": "The matched route (path template in the format used by the respective server framework). Examples: * /graphql Requirement level: Conditionally Required: If and only if it’s available", - "type": "boolean", - "nullable": true - }, - "network.local.address": { - "description": "Local socket address. Useful in case of a multi-IP host. Examples: * 10.1.2.80 * /tmp/my.sock Requirement level: Opt-In", - "type": "boolean", - "nullable": true - }, - "network.local.port": { - "description": "Local socket port. Useful in case of a multi-port host. Examples: * 65123 Requirement level: Opt-In", - "type": "boolean", - "nullable": true - }, - "network.peer.address": { - "description": "Peer address of the network connection - IP address or Unix domain socket name. Examples: * 10.1.2.80 * /tmp/my.sock Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "network.peer.port": { - "description": "Peer port number of the network connection. Examples: * 65123 Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "network.protocol.name": { - "description": "OSI application layer or non-OSI equivalent. Examples: * http * spdy Requirement level: Recommended: if not default (http).", - "type": "boolean", - "nullable": true - }, - "network.protocol.version": { - "description": "Version of the protocol specified in network.protocol.name. Examples: * 1.0 * 1.1 * 2 * 3 Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "network.transport": { - "description": "OSI transport layer. Examples: * tcp * udp Requirement level: Conditionally Required", - "type": "boolean", - "nullable": true - }, - "network.type": { - "description": "OSI network layer or non-OSI equivalent. Examples: * ipv4 * ipv6 Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "server.address": { - "description": "Name of the local HTTP server that received the request. Examples: * example.com * 10.1.2.80 * /tmp/my.sock Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "server.port": { - "description": "Port of the local HTTP server that received the request. Examples: * 80 * 8080 * 443 Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "trace_id": { - "description": "The OpenTelemetry trace ID. This can be output in logs.", - "type": "boolean", - "nullable": true - }, - "url.path": { - "description": "The URI path component Examples: * /search Requirement level: Required", - "type": "boolean", - "nullable": true - }, - "url.query": { - "description": "The URI query component Examples: * q=OpenTelemetry Requirement level: Conditionally Required: If and only if one was received/sent.", - "type": "boolean", - "nullable": true - }, - "url.scheme": { - "description": "The URI scheme component identifying the used protocol. Examples: * http * https Requirement level: Required", - "type": "boolean", - "nullable": true - }, - "user_agent.original": { - "description": "Value of the HTTP User-Agent header sent by the client. Examples: * CERN-LineMode/2.15 * libwww/2.17b3 Requirement level: Recommended", - "type": "boolean", - "nullable": true - } - }, - "additionalProperties": { - "anyOf": [ - { - "description": "A header from the request", - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "The request method.", - "type": "object", - "required": [ - "request_method" - ], - "properties": { - "request_method": { - "description": "The request method enabled or not", - "type": "boolean" - } - }, - "additionalProperties": false - }, - { - "description": "A header from the response", - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "The trace ID of the request.", - "type": "object", - "required": [ - "trace_id" - ], - "properties": { - "trace_id": { - "description": "The format of the trace ID.", - "oneOf": [ - { - "description": "Open Telemetry trace ID, a hex string.", - "type": "string", - "enum": [ - "open_telemetry" - ] - }, - { - "description": "Datadog trace ID, a u64.", - "type": "string", - "enum": [ - "datadog" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "A value from context.", - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A value from baggage.", - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "description": "A value from an environment variable.", - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "on_graphql_error" - ], - "properties": { - "on_graphql_error": { - "description": "Boolean set to true if the response body contains graphql error", - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - } - }, - "condition": { - "description": "The event conditions.", - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "description": "A header from the request", - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "The request method.", - "type": "object", - "required": [ - "request_method" - ], - "properties": { - "request_method": { - "description": "The request method enabled or not", - "type": "boolean" - } - }, - "additionalProperties": false - }, - { - "description": "A header from the response", - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "The trace ID of the request.", - "type": "object", - "required": [ - "trace_id" - ], - "properties": { - "trace_id": { - "description": "The format of the trace ID.", - "oneOf": [ - { - "description": "Open Telemetry trace ID, a hex string.", - "type": "string", - "enum": [ - "open_telemetry" - ] - }, - { - "description": "Datadog trace ID, a u64.", - "type": "string", - "enum": [ - "datadog" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "A value from context.", - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A value from baggage.", - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "description": "A value from an environment variable.", - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "on_graphql_error" - ], - "properties": { - "on_graphql_error": { - "description": "Boolean set to true if the response body contains graphql error", - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "description": "A header from the request", - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "The request method.", - "type": "object", - "required": [ - "request_method" - ], - "properties": { - "request_method": { - "description": "The request method enabled or not", - "type": "boolean" - } - }, - "additionalProperties": false - }, - { - "description": "A header from the response", - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "The trace ID of the request.", - "type": "object", - "required": [ - "trace_id" - ], - "properties": { - "trace_id": { - "description": "The format of the trace ID.", - "oneOf": [ - { - "description": "Open Telemetry trace ID, a hex string.", - "type": "string", - "enum": [ - "open_telemetry" - ] - }, - { - "description": "Datadog trace ID, a u64.", - "type": "string", - "enum": [ - "datadog" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "A value from context.", - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A value from baggage.", - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "description": "A value from an environment variable.", - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "on_graphql_error" - ], - "properties": { - "on_graphql_error": { - "description": "Boolean set to true if the response body contains graphql error", - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_RouterSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_RouterSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_RouterSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "level": { - "description": "The log level of the event.", - "type": "string", - "enum": [ - "info", - "warn", - "error", - "off" - ] - }, - "message": { - "description": "The event message.", - "type": "string" - }, - "on": { - "description": "When to trigger the event.", - "oneOf": [ - { - "description": "Log the event on request", - "type": "string", - "enum": [ - "request" - ] - }, - { - "description": "Log the event on response", - "type": "string", - "enum": [ - "response" - ] - }, - { - "description": "Log the event on error", - "type": "string", - "enum": [ - "error" - ] - } - ] - } - } - } - }, - "subgraph": { - "description": "Supergraph service events", - "type": "object", - "properties": { - "error": { - "description": "Log the subgraph error", - "type": "string", - "enum": [ - "info", - "warn", - "error", - "off" - ] - }, - "request": { - "description": "Log the subgraph request", - "type": "string", - "enum": [ - "info", - "warn", - "error", - "off" - ] - }, - "response": { - "description": "Log the subgraph response", - "type": "string", - "enum": [ - "info", - "warn", - "error", - "off" - ] - } - }, - "additionalProperties": { - "description": "An event that can be logged as part of a trace. The event has an implicit `type` attribute that matches the name of the event in the yaml and a message that can be used to provide additional information.", - "type": "object", - "required": [ - "level", - "message", - "on" - ], - "properties": { - "attributes": { - "description": "The event attributes.", - "type": "object", - "properties": { - "subgraph.graphql.document": { - "description": "The GraphQL document being executed. Examples: * query findBookById { bookById(id: ?) { name } } Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "subgraph.graphql.operation.name": { - "description": "The name of the operation being executed. Examples: * findBookById Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "subgraph.graphql.operation.type": { - "description": "The type of the operation being executed. Examples: * query * subscription * mutation Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "subgraph.name": { - "description": "The name of the subgraph Examples: * products Requirement level: Required", - "type": "boolean", - "nullable": true - } - }, - "additionalProperties": { - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - }, - "condition": { - "description": "The event conditions.", - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "level": { - "description": "The log level of the event.", - "type": "string", - "enum": [ - "info", - "warn", - "error", - "off" - ] - }, - "message": { - "description": "The event message.", - "type": "string" - }, - "on": { - "description": "When to trigger the event.", - "oneOf": [ - { - "description": "Log the event on request", - "type": "string", - "enum": [ - "request" - ] - }, - { - "description": "Log the event on response", - "type": "string", - "enum": [ - "response" - ] - }, - { - "description": "Log the event on error", - "type": "string", - "enum": [ - "error" - ] - } - ] - } - } - } - }, - "supergraph": { - "description": "Subgraph service events", - "type": "object", - "properties": { - "error": { - "description": "Log the supergraph error", - "type": "string", - "enum": [ - "info", - "warn", - "error", - "off" - ] - }, - "request": { - "description": "Log the supergraph request", - "type": "string", - "enum": [ - "info", - "warn", - "error", - "off" - ] - }, - "response": { - "description": "Log the supergraph response", - "type": "string", - "enum": [ - "info", - "warn", - "error", - "off" - ] - } - }, - "additionalProperties": { - "description": "An event that can be logged as part of a trace. The event has an implicit `type` attribute that matches the name of the event in the yaml and a message that can be used to provide additional information.", - "type": "object", - "required": [ - "level", - "message", - "on" - ], - "properties": { - "attributes": { - "description": "The event attributes.", - "type": "object", - "properties": { - "cost.actual": { - "description": "The actual cost of the operation using the currently configured cost model", - "type": "boolean", - "nullable": true - }, - "cost.delta": { - "description": "The delta (estimated - actual) cost of the operation using the currently configured cost model", - "type": "boolean", - "nullable": true - }, - "cost.estimated": { - "description": "The estimated cost of the operation using the currently configured cost model", - "type": "boolean", - "nullable": true - }, - "cost.result": { - "description": "The cost result, this is an error code returned by the cost calculation or COST_OK", - "type": "boolean", - "nullable": true - }, - "graphql.document": { - "description": "The GraphQL document being executed. Examples: * query findBookById { bookById(id: ?) { name } } Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "graphql.operation.name": { - "description": "The name of the operation being executed. Examples: * findBookById Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "graphql.operation.type": { - "description": "The type of the operation being executed. Examples: * query * subscription * mutation Requirement level: Recommended", - "type": "boolean", - "nullable": true - } - }, - "additionalProperties": { - "anyOf": [ - { - "type": "object", - "required": [ - "operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "operation_name": { - "description": "The operation name from the query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "operation_kind" - ], - "properties": { - "operation_kind": { - "description": "The operation kind from the query (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "query": { - "description": "The graphql query.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "query_variable": { - "description": "The name of a graphql query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "response_header": { - "description": "The name of the response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Cost attributes", - "type": "object", - "required": [ - "cost" - ], - "properties": { - "cost": { - "description": "The cost value to select, one of: estimated, actual, delta.", - "oneOf": [ - { - "description": "The estimated cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "estimated" - ] - }, - { - "description": "The actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "actual" - ] - }, - { - "description": "The delta between the estimated and actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "delta" - ] - }, - { - "description": "The result of the cost calculation. This is the error code returned by the cost calculation.", - "type": "string", - "enum": [ - "result" - ] - } - ] - } - }, - "additionalProperties": false - } - ] - } - }, - "condition": { - "description": "The event conditions.", - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "type": "object", - "required": [ - "operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "operation_name": { - "description": "The operation name from the query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "operation_kind" - ], - "properties": { - "operation_kind": { - "description": "The operation kind from the query (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "query": { - "description": "The graphql query.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "query_variable": { - "description": "The name of a graphql query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "response_header": { - "description": "The name of the response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Cost attributes", - "type": "object", - "required": [ - "cost" - ], - "properties": { - "cost": { - "description": "The cost value to select, one of: estimated, actual, delta.", - "oneOf": [ - { - "description": "The estimated cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "estimated" - ] - }, - { - "description": "The actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "actual" - ] - }, - { - "description": "The delta between the estimated and actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "delta" - ] - }, - { - "description": "The result of the cost calculation. This is the error code returned by the cost calculation.", - "type": "string", - "enum": [ - "result" - ] - } - ] - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "type": "object", - "required": [ - "operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "operation_name": { - "description": "The operation name from the query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "operation_kind" - ], - "properties": { - "operation_kind": { - "description": "The operation kind from the query (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "query": { - "description": "The graphql query.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "query_variable": { - "description": "The name of a graphql query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "response_header": { - "description": "The name of the response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Cost attributes", - "type": "object", - "required": [ - "cost" - ], - "properties": { - "cost": { - "description": "The cost value to select, one of: estimated, actual, delta.", - "oneOf": [ - { - "description": "The estimated cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "estimated" - ] - }, - { - "description": "The actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "actual" - ] - }, - { - "description": "The delta between the estimated and actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "delta" - ] - }, - { - "description": "The result of the cost calculation. This is the error code returned by the cost calculation.", - "type": "string", - "enum": [ - "result" - ] - } - ] - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SupergraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SupergraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_SupergraphSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "level": { - "description": "The log level of the event.", - "type": "string", - "enum": [ - "info", - "warn", - "error", - "off" - ] - }, - "message": { - "description": "The event message.", - "type": "string" - }, - "on": { - "description": "When to trigger the event.", - "oneOf": [ - { - "description": "Log the event on request", - "type": "string", - "enum": [ - "request" - ] - }, - { - "description": "Log the event on response", - "type": "string", - "enum": [ - "response" - ] - }, - { - "description": "Log the event on error", - "type": "string", - "enum": [ - "error" - ] - } - ] - } - } - } - } - }, - "additionalProperties": false - }, - "instruments": { - "description": "Instrument configuration", - "type": "object", - "properties": { - "default_requirement_level": { - "description": "The attributes and instruments to include by default in instruments based on their level as specified in the otel semantic conventions and Apollo documentation.", - "oneOf": [ - { - "description": "No default attributes set on spans, you have to set it one by one in the configuration to enable some attributes", - "type": "string", - "enum": [ - "none" - ] - }, - { - "description": "Attributes that are marked as required in otel semantic conventions and apollo documentation will be included (default)", - "type": "string", - "enum": [ - "required" - ] - }, - { - "description": "Attributes that are marked as required or recommended in otel semantic conventions and apollo documentation will be included", - "type": "string", - "enum": [ - "recommended" - ] - } - ] - }, - "router": { - "description": "Router service instruments. For more information see documentation on Router lifecycle.", - "type": "object", - "properties": { - "http.server.active_requests": { - "description": "Counter of active requests", - "anyOf": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "object", - "required": [ - "attributes" - ], - "properties": { - "attributes": { - "type": "object", - "properties": { - "http.request.method": { - "default": false, - "type": "boolean" - }, - "server.address": { - "default": false, - "type": "boolean" - }, - "server.port": { - "default": false, - "type": "boolean" - }, - "url.scheme": { - "default": false, - "type": "boolean" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "http.server.request.body.size": { - "description": "Histogram of server request body size", - "anyOf": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "object", - "required": [ - "attributes" - ], - "properties": { - "attributes": { - "description": "Common attributes for http server and client. See https://opentelemetry.io/docs/specs/semconv/http/http-spans/#common-attributes", - "type": "object", - "properties": { - "baggage": { - "description": "All key values from trace baggage.", - "type": "boolean", - "nullable": true - }, - "dd.trace_id": { - "description": "The datadog trace ID. This can be output in logs and used to correlate traces in Datadog.", - "type": "boolean", - "nullable": true - }, - "error.type": { - "description": "Describes a class of error the operation ended with. Examples: * timeout * name_resolution_error * 500 Requirement level: Conditionally Required: If request has ended with an error.", - "type": "boolean", - "nullable": true - }, - "http.request.body.size": { - "description": "The size of the request payload body in bytes. This is the number of bytes transferred excluding headers and is often, but not always, present as the Content-Length header. For requests using transport encoding, this should be the compressed size. Examples: * 3495 Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "http.request.method": { - "description": "HTTP request method. Examples: * GET * POST * HEAD Requirement level: Required", - "type": "boolean", - "nullable": true - }, - "http.response.body.size": { - "description": "The size of the response payload body in bytes. This is the number of bytes transferred excluding headers and is often, but not always, present as the Content-Length header. For requests using transport encoding, this should be the compressed size. Examples: * 3495 Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "http.response.status_code": { - "description": "HTTP response status code. Examples: * 200 Requirement level: Conditionally Required: If and only if one was received/sent.", - "type": "boolean", - "nullable": true - }, - "http.route": { - "description": "The matched route (path template in the format used by the respective server framework). Examples: * /graphql Requirement level: Conditionally Required: If and only if it’s available", - "type": "boolean", - "nullable": true - }, - "network.local.address": { - "description": "Local socket address. Useful in case of a multi-IP host. Examples: * 10.1.2.80 * /tmp/my.sock Requirement level: Opt-In", - "type": "boolean", - "nullable": true - }, - "network.local.port": { - "description": "Local socket port. Useful in case of a multi-port host. Examples: * 65123 Requirement level: Opt-In", - "type": "boolean", - "nullable": true - }, - "network.peer.address": { - "description": "Peer address of the network connection - IP address or Unix domain socket name. Examples: * 10.1.2.80 * /tmp/my.sock Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "network.peer.port": { - "description": "Peer port number of the network connection. Examples: * 65123 Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "network.protocol.name": { - "description": "OSI application layer or non-OSI equivalent. Examples: * http * spdy Requirement level: Recommended: if not default (http).", - "type": "boolean", - "nullable": true - }, - "network.protocol.version": { - "description": "Version of the protocol specified in network.protocol.name. Examples: * 1.0 * 1.1 * 2 * 3 Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "network.transport": { - "description": "OSI transport layer. Examples: * tcp * udp Requirement level: Conditionally Required", - "type": "boolean", - "nullable": true - }, - "network.type": { - "description": "OSI network layer or non-OSI equivalent. Examples: * ipv4 * ipv6 Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "server.address": { - "description": "Name of the local HTTP server that received the request. Examples: * example.com * 10.1.2.80 * /tmp/my.sock Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "server.port": { - "description": "Port of the local HTTP server that received the request. Examples: * 80 * 8080 * 443 Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "trace_id": { - "description": "The OpenTelemetry trace ID. This can be output in logs.", - "type": "boolean", - "nullable": true - }, - "url.path": { - "description": "The URI path component Examples: * /search Requirement level: Required", - "type": "boolean", - "nullable": true - }, - "url.query": { - "description": "The URI query component Examples: * q=OpenTelemetry Requirement level: Conditionally Required: If and only if one was received/sent.", - "type": "boolean", - "nullable": true - }, - "url.scheme": { - "description": "The URI scheme component identifying the used protocol. Examples: * http * https Requirement level: Required", - "type": "boolean", - "nullable": true - }, - "user_agent.original": { - "description": "Value of the HTTP User-Agent header sent by the client. Examples: * CERN-LineMode/2.15 * libwww/2.17b3 Requirement level: Recommended", - "type": "boolean", - "nullable": true - } - }, - "additionalProperties": { - "anyOf": [ - { - "description": "A header from the request", - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "The request method.", - "type": "object", - "required": [ - "request_method" - ], - "properties": { - "request_method": { - "description": "The request method enabled or not", - "type": "boolean" - } - }, - "additionalProperties": false - }, - { - "description": "A header from the response", - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "The trace ID of the request.", - "type": "object", - "required": [ - "trace_id" - ], - "properties": { - "trace_id": { - "description": "The format of the trace ID.", - "oneOf": [ - { - "description": "Open Telemetry trace ID, a hex string.", - "type": "string", - "enum": [ - "open_telemetry" - ] - }, - { - "description": "Datadog trace ID, a u64.", - "type": "string", - "enum": [ - "datadog" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "A value from context.", - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A value from baggage.", - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "description": "A value from an environment variable.", - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "on_graphql_error" - ], - "properties": { - "on_graphql_error": { - "description": "Boolean set to true if the response body contains graphql error", - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - } - } - }, - "additionalProperties": false - } - ] - }, - "http.server.request.duration": { - "description": "Histogram of server request duration", - "anyOf": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "object", - "required": [ - "attributes" - ], - "properties": { - "attributes": { - "description": "Common attributes for http server and client. See https://opentelemetry.io/docs/specs/semconv/http/http-spans/#common-attributes", - "type": "object", - "properties": { - "baggage": { - "description": "All key values from trace baggage.", - "type": "boolean", - "nullable": true - }, - "dd.trace_id": { - "description": "The datadog trace ID. This can be output in logs and used to correlate traces in Datadog.", - "type": "boolean", - "nullable": true - }, - "error.type": { - "description": "Describes a class of error the operation ended with. Examples: * timeout * name_resolution_error * 500 Requirement level: Conditionally Required: If request has ended with an error.", - "type": "boolean", - "nullable": true - }, - "http.request.body.size": { - "description": "The size of the request payload body in bytes. This is the number of bytes transferred excluding headers and is often, but not always, present as the Content-Length header. For requests using transport encoding, this should be the compressed size. Examples: * 3495 Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "http.request.method": { - "description": "HTTP request method. Examples: * GET * POST * HEAD Requirement level: Required", - "type": "boolean", - "nullable": true - }, - "http.response.body.size": { - "description": "The size of the response payload body in bytes. This is the number of bytes transferred excluding headers and is often, but not always, present as the Content-Length header. For requests using transport encoding, this should be the compressed size. Examples: * 3495 Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "http.response.status_code": { - "description": "HTTP response status code. Examples: * 200 Requirement level: Conditionally Required: If and only if one was received/sent.", - "type": "boolean", - "nullable": true - }, - "http.route": { - "description": "The matched route (path template in the format used by the respective server framework). Examples: * /graphql Requirement level: Conditionally Required: If and only if it’s available", - "type": "boolean", - "nullable": true - }, - "network.local.address": { - "description": "Local socket address. Useful in case of a multi-IP host. Examples: * 10.1.2.80 * /tmp/my.sock Requirement level: Opt-In", - "type": "boolean", - "nullable": true - }, - "network.local.port": { - "description": "Local socket port. Useful in case of a multi-port host. Examples: * 65123 Requirement level: Opt-In", - "type": "boolean", - "nullable": true - }, - "network.peer.address": { - "description": "Peer address of the network connection - IP address or Unix domain socket name. Examples: * 10.1.2.80 * /tmp/my.sock Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "network.peer.port": { - "description": "Peer port number of the network connection. Examples: * 65123 Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "network.protocol.name": { - "description": "OSI application layer or non-OSI equivalent. Examples: * http * spdy Requirement level: Recommended: if not default (http).", - "type": "boolean", - "nullable": true - }, - "network.protocol.version": { - "description": "Version of the protocol specified in network.protocol.name. Examples: * 1.0 * 1.1 * 2 * 3 Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "network.transport": { - "description": "OSI transport layer. Examples: * tcp * udp Requirement level: Conditionally Required", - "type": "boolean", - "nullable": true - }, - "network.type": { - "description": "OSI network layer or non-OSI equivalent. Examples: * ipv4 * ipv6 Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "server.address": { - "description": "Name of the local HTTP server that received the request. Examples: * example.com * 10.1.2.80 * /tmp/my.sock Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "server.port": { - "description": "Port of the local HTTP server that received the request. Examples: * 80 * 8080 * 443 Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "trace_id": { - "description": "The OpenTelemetry trace ID. This can be output in logs.", - "type": "boolean", - "nullable": true - }, - "url.path": { - "description": "The URI path component Examples: * /search Requirement level: Required", - "type": "boolean", - "nullable": true - }, - "url.query": { - "description": "The URI query component Examples: * q=OpenTelemetry Requirement level: Conditionally Required: If and only if one was received/sent.", - "type": "boolean", - "nullable": true - }, - "url.scheme": { - "description": "The URI scheme component identifying the used protocol. Examples: * http * https Requirement level: Required", - "type": "boolean", - "nullable": true - }, - "user_agent.original": { - "description": "Value of the HTTP User-Agent header sent by the client. Examples: * CERN-LineMode/2.15 * libwww/2.17b3 Requirement level: Recommended", - "type": "boolean", - "nullable": true - } - }, - "additionalProperties": { - "anyOf": [ - { - "description": "A header from the request", - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "The request method.", - "type": "object", - "required": [ - "request_method" - ], - "properties": { - "request_method": { - "description": "The request method enabled or not", - "type": "boolean" - } - }, - "additionalProperties": false - }, - { - "description": "A header from the response", - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "The trace ID of the request.", - "type": "object", - "required": [ - "trace_id" - ], - "properties": { - "trace_id": { - "description": "The format of the trace ID.", - "oneOf": [ - { - "description": "Open Telemetry trace ID, a hex string.", - "type": "string", - "enum": [ - "open_telemetry" - ] - }, - { - "description": "Datadog trace ID, a u64.", - "type": "string", - "enum": [ - "datadog" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "A value from context.", - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A value from baggage.", - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "description": "A value from an environment variable.", - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "on_graphql_error" - ], - "properties": { - "on_graphql_error": { - "description": "Boolean set to true if the response body contains graphql error", - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - } - } - }, - "additionalProperties": false - } - ] - }, - "http.server.response.body.size": { - "description": "Histogram of server response body size", - "anyOf": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "object", - "required": [ - "attributes" - ], - "properties": { - "attributes": { - "description": "Common attributes for http server and client. See https://opentelemetry.io/docs/specs/semconv/http/http-spans/#common-attributes", - "type": "object", - "properties": { - "baggage": { - "description": "All key values from trace baggage.", - "type": "boolean", - "nullable": true - }, - "dd.trace_id": { - "description": "The datadog trace ID. This can be output in logs and used to correlate traces in Datadog.", - "type": "boolean", - "nullable": true - }, - "error.type": { - "description": "Describes a class of error the operation ended with. Examples: * timeout * name_resolution_error * 500 Requirement level: Conditionally Required: If request has ended with an error.", - "type": "boolean", - "nullable": true - }, - "http.request.body.size": { - "description": "The size of the request payload body in bytes. This is the number of bytes transferred excluding headers and is often, but not always, present as the Content-Length header. For requests using transport encoding, this should be the compressed size. Examples: * 3495 Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "http.request.method": { - "description": "HTTP request method. Examples: * GET * POST * HEAD Requirement level: Required", - "type": "boolean", - "nullable": true - }, - "http.response.body.size": { - "description": "The size of the response payload body in bytes. This is the number of bytes transferred excluding headers and is often, but not always, present as the Content-Length header. For requests using transport encoding, this should be the compressed size. Examples: * 3495 Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "http.response.status_code": { - "description": "HTTP response status code. Examples: * 200 Requirement level: Conditionally Required: If and only if one was received/sent.", - "type": "boolean", - "nullable": true - }, - "http.route": { - "description": "The matched route (path template in the format used by the respective server framework). Examples: * /graphql Requirement level: Conditionally Required: If and only if it’s available", - "type": "boolean", - "nullable": true - }, - "network.local.address": { - "description": "Local socket address. Useful in case of a multi-IP host. Examples: * 10.1.2.80 * /tmp/my.sock Requirement level: Opt-In", - "type": "boolean", - "nullable": true - }, - "network.local.port": { - "description": "Local socket port. Useful in case of a multi-port host. Examples: * 65123 Requirement level: Opt-In", - "type": "boolean", - "nullable": true - }, - "network.peer.address": { - "description": "Peer address of the network connection - IP address or Unix domain socket name. Examples: * 10.1.2.80 * /tmp/my.sock Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "network.peer.port": { - "description": "Peer port number of the network connection. Examples: * 65123 Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "network.protocol.name": { - "description": "OSI application layer or non-OSI equivalent. Examples: * http * spdy Requirement level: Recommended: if not default (http).", - "type": "boolean", - "nullable": true - }, - "network.protocol.version": { - "description": "Version of the protocol specified in network.protocol.name. Examples: * 1.0 * 1.1 * 2 * 3 Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "network.transport": { - "description": "OSI transport layer. Examples: * tcp * udp Requirement level: Conditionally Required", - "type": "boolean", - "nullable": true - }, - "network.type": { - "description": "OSI network layer or non-OSI equivalent. Examples: * ipv4 * ipv6 Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "server.address": { - "description": "Name of the local HTTP server that received the request. Examples: * example.com * 10.1.2.80 * /tmp/my.sock Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "server.port": { - "description": "Port of the local HTTP server that received the request. Examples: * 80 * 8080 * 443 Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "trace_id": { - "description": "The OpenTelemetry trace ID. This can be output in logs.", - "type": "boolean", - "nullable": true - }, - "url.path": { - "description": "The URI path component Examples: * /search Requirement level: Required", - "type": "boolean", - "nullable": true - }, - "url.query": { - "description": "The URI query component Examples: * q=OpenTelemetry Requirement level: Conditionally Required: If and only if one was received/sent.", - "type": "boolean", - "nullable": true - }, - "url.scheme": { - "description": "The URI scheme component identifying the used protocol. Examples: * http * https Requirement level: Required", - "type": "boolean", - "nullable": true - }, - "user_agent.original": { - "description": "Value of the HTTP User-Agent header sent by the client. Examples: * CERN-LineMode/2.15 * libwww/2.17b3 Requirement level: Recommended", - "type": "boolean", - "nullable": true - } - }, - "additionalProperties": { - "anyOf": [ - { - "description": "A header from the request", - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "The request method.", - "type": "object", - "required": [ - "request_method" - ], - "properties": { - "request_method": { - "description": "The request method enabled or not", - "type": "boolean" - } - }, - "additionalProperties": false - }, - { - "description": "A header from the response", - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "The trace ID of the request.", - "type": "object", - "required": [ - "trace_id" - ], - "properties": { - "trace_id": { - "description": "The format of the trace ID.", - "oneOf": [ - { - "description": "Open Telemetry trace ID, a hex string.", - "type": "string", - "enum": [ - "open_telemetry" - ] - }, - { - "description": "Datadog trace ID, a u64.", - "type": "string", - "enum": [ - "datadog" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "A value from context.", - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A value from baggage.", - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "description": "A value from an environment variable.", - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "on_graphql_error" - ], - "properties": { - "on_graphql_error": { - "description": "Boolean set to true if the response body contains graphql error", - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - } - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": { - "type": "object", - "required": [ - "description", - "type", - "unit", - "value" - ], - "properties": { - "attributes": { - "description": "Attributes to include on the instrument.", - "type": "object", - "properties": { - "baggage": { - "description": "All key values from trace baggage.", - "type": "boolean", - "nullable": true - }, - "dd.trace_id": { - "description": "The datadog trace ID. This can be output in logs and used to correlate traces in Datadog.", - "type": "boolean", - "nullable": true - }, - "error.type": { - "description": "Describes a class of error the operation ended with. Examples: * timeout * name_resolution_error * 500 Requirement level: Conditionally Required: If request has ended with an error.", - "type": "boolean", - "nullable": true - }, - "http.request.body.size": { - "description": "The size of the request payload body in bytes. This is the number of bytes transferred excluding headers and is often, but not always, present as the Content-Length header. For requests using transport encoding, this should be the compressed size. Examples: * 3495 Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "http.request.method": { - "description": "HTTP request method. Examples: * GET * POST * HEAD Requirement level: Required", - "type": "boolean", - "nullable": true - }, - "http.response.body.size": { - "description": "The size of the response payload body in bytes. This is the number of bytes transferred excluding headers and is often, but not always, present as the Content-Length header. For requests using transport encoding, this should be the compressed size. Examples: * 3495 Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "http.response.status_code": { - "description": "HTTP response status code. Examples: * 200 Requirement level: Conditionally Required: If and only if one was received/sent.", - "type": "boolean", - "nullable": true - }, - "http.route": { - "description": "The matched route (path template in the format used by the respective server framework). Examples: * /graphql Requirement level: Conditionally Required: If and only if it’s available", - "type": "boolean", - "nullable": true - }, - "network.local.address": { - "description": "Local socket address. Useful in case of a multi-IP host. Examples: * 10.1.2.80 * /tmp/my.sock Requirement level: Opt-In", - "type": "boolean", - "nullable": true - }, - "network.local.port": { - "description": "Local socket port. Useful in case of a multi-port host. Examples: * 65123 Requirement level: Opt-In", - "type": "boolean", - "nullable": true - }, - "network.peer.address": { - "description": "Peer address of the network connection - IP address or Unix domain socket name. Examples: * 10.1.2.80 * /tmp/my.sock Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "network.peer.port": { - "description": "Peer port number of the network connection. Examples: * 65123 Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "network.protocol.name": { - "description": "OSI application layer or non-OSI equivalent. Examples: * http * spdy Requirement level: Recommended: if not default (http).", - "type": "boolean", - "nullable": true - }, - "network.protocol.version": { - "description": "Version of the protocol specified in network.protocol.name. Examples: * 1.0 * 1.1 * 2 * 3 Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "network.transport": { - "description": "OSI transport layer. Examples: * tcp * udp Requirement level: Conditionally Required", - "type": "boolean", - "nullable": true - }, - "network.type": { - "description": "OSI network layer or non-OSI equivalent. Examples: * ipv4 * ipv6 Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "server.address": { - "description": "Name of the local HTTP server that received the request. Examples: * example.com * 10.1.2.80 * /tmp/my.sock Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "server.port": { - "description": "Port of the local HTTP server that received the request. Examples: * 80 * 8080 * 443 Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "trace_id": { - "description": "The OpenTelemetry trace ID. This can be output in logs.", - "type": "boolean", - "nullable": true - }, - "url.path": { - "description": "The URI path component Examples: * /search Requirement level: Required", - "type": "boolean", - "nullable": true - }, - "url.query": { - "description": "The URI query component Examples: * q=OpenTelemetry Requirement level: Conditionally Required: If and only if one was received/sent.", - "type": "boolean", - "nullable": true - }, - "url.scheme": { - "description": "The URI scheme component identifying the used protocol. Examples: * http * https Requirement level: Required", - "type": "boolean", - "nullable": true - }, - "user_agent.original": { - "description": "Value of the HTTP User-Agent header sent by the client. Examples: * CERN-LineMode/2.15 * libwww/2.17b3 Requirement level: Recommended", - "type": "boolean", - "nullable": true - } - }, - "additionalProperties": { - "anyOf": [ - { - "description": "A header from the request", - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "The request method.", - "type": "object", - "required": [ - "request_method" - ], - "properties": { - "request_method": { - "description": "The request method enabled or not", - "type": "boolean" - } - }, - "additionalProperties": false - }, - { - "description": "A header from the response", - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "The trace ID of the request.", - "type": "object", - "required": [ - "trace_id" - ], - "properties": { - "trace_id": { - "description": "The format of the trace ID.", - "oneOf": [ - { - "description": "Open Telemetry trace ID, a hex string.", - "type": "string", - "enum": [ - "open_telemetry" - ] - }, - { - "description": "Datadog trace ID, a u64.", - "type": "string", - "enum": [ - "datadog" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "A value from context.", - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A value from baggage.", - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "description": "A value from an environment variable.", - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "on_graphql_error" - ], - "properties": { - "on_graphql_error": { - "description": "Boolean set to true if the response body contains graphql error", - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - } - }, - "condition": { - "description": "The instrument conditions.", - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "description": "A header from the request", - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "The request method.", - "type": "object", - "required": [ - "request_method" - ], - "properties": { - "request_method": { - "description": "The request method enabled or not", - "type": "boolean" - } - }, - "additionalProperties": false - }, - { - "description": "A header from the response", - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "The trace ID of the request.", - "type": "object", - "required": [ - "trace_id" - ], - "properties": { - "trace_id": { - "description": "The format of the trace ID.", - "oneOf": [ - { - "description": "Open Telemetry trace ID, a hex string.", - "type": "string", - "enum": [ - "open_telemetry" - ] - }, - { - "description": "Datadog trace ID, a u64.", - "type": "string", - "enum": [ - "datadog" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "A value from context.", - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A value from baggage.", - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "description": "A value from an environment variable.", - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "on_graphql_error" - ], - "properties": { - "on_graphql_error": { - "description": "Boolean set to true if the response body contains graphql error", - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "description": "A header from the request", - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "The request method.", - "type": "object", - "required": [ - "request_method" - ], - "properties": { - "request_method": { - "description": "The request method enabled or not", - "type": "boolean" - } - }, - "additionalProperties": false - }, - { - "description": "A header from the response", - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "The trace ID of the request.", - "type": "object", - "required": [ - "trace_id" - ], - "properties": { - "trace_id": { - "description": "The format of the trace ID.", - "oneOf": [ - { - "description": "Open Telemetry trace ID, a hex string.", - "type": "string", - "enum": [ - "open_telemetry" - ] - }, - { - "description": "Datadog trace ID, a u64.", - "type": "string", - "enum": [ - "datadog" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "A value from context.", - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A value from baggage.", - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "description": "A value from an environment variable.", - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "on_graphql_error" - ], - "properties": { - "on_graphql_error": { - "description": "Boolean set to true if the response body contains graphql error", - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_RouterSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_RouterSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_RouterSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "description": { - "description": "The description of the instrument.", - "type": "string" - }, - "type": { - "description": "The type of instrument.", - "oneOf": [ - { - "description": "A monotonic counter https://opentelemetry.io/docs/specs/otel/metrics/data-model/#sums", - "type": "string", - "enum": [ - "counter" - ] - }, - { - "description": "A histogram https://opentelemetry.io/docs/specs/otel/metrics/data-model/#histogram", - "type": "string", - "enum": [ - "histogram" - ] - } - ] - }, - "unit": { - "description": "The units of the instrument, e.g. \"ms\", \"bytes\", \"requests\".", - "type": "string" - }, - "value": { - "description": "The value of the instrument.", - "anyOf": [ - { - "type": "string", - "enum": [ - "duration", - "unit" - ] - }, - { - "anyOf": [ - { - "description": "A header from the request", - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "The request method.", - "type": "object", - "required": [ - "request_method" - ], - "properties": { - "request_method": { - "description": "The request method enabled or not", - "type": "boolean" - } - }, - "additionalProperties": false - }, - { - "description": "A header from the response", - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "The trace ID of the request.", - "type": "object", - "required": [ - "trace_id" - ], - "properties": { - "trace_id": { - "description": "The format of the trace ID.", - "oneOf": [ - { - "description": "Open Telemetry trace ID, a hex string.", - "type": "string", - "enum": [ - "open_telemetry" - ] - }, - { - "description": "Datadog trace ID, a u64.", - "type": "string", - "enum": [ - "datadog" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "A value from context.", - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A value from baggage.", - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "description": "A value from an environment variable.", - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "on_graphql_error" - ], - "properties": { - "on_graphql_error": { - "description": "Boolean set to true if the response body contains graphql error", - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - } - ] - } - } - } - }, - "subgraph": { - "description": "Subgraph service instruments. For more information see documentation on Router lifecycle.", - "type": "object", - "properties": { - "http.client.request.body.size": { - "description": "Histogram of client request body size", - "anyOf": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "object", - "required": [ - "attributes" - ], - "properties": { - "attributes": { - "type": "object", - "properties": { - "subgraph.graphql.document": { - "description": "The GraphQL document being executed. Examples: * query findBookById { bookById(id: ?) { name } } Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "subgraph.graphql.operation.name": { - "description": "The name of the operation being executed. Examples: * findBookById Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "subgraph.graphql.operation.type": { - "description": "The type of the operation being executed. Examples: * query * subscription * mutation Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "subgraph.name": { - "description": "The name of the subgraph Examples: * products Requirement level: Required", - "type": "boolean", - "nullable": true - } - }, - "additionalProperties": { - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - } - }, - "additionalProperties": false - } - ] - }, - "http.client.request.duration": { - "description": "Histogram of client request duration", - "anyOf": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "object", - "required": [ - "attributes" - ], - "properties": { - "attributes": { - "type": "object", - "properties": { - "subgraph.graphql.document": { - "description": "The GraphQL document being executed. Examples: * query findBookById { bookById(id: ?) { name } } Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "subgraph.graphql.operation.name": { - "description": "The name of the operation being executed. Examples: * findBookById Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "subgraph.graphql.operation.type": { - "description": "The type of the operation being executed. Examples: * query * subscription * mutation Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "subgraph.name": { - "description": "The name of the subgraph Examples: * products Requirement level: Required", - "type": "boolean", - "nullable": true - } - }, - "additionalProperties": { - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - } - }, - "additionalProperties": false - } - ] - }, - "http.client.response.body.size": { - "description": "Histogram of client response body size", - "anyOf": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "object", - "required": [ - "attributes" - ], - "properties": { - "attributes": { - "type": "object", - "properties": { - "subgraph.graphql.document": { - "description": "The GraphQL document being executed. Examples: * query findBookById { bookById(id: ?) { name } } Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "subgraph.graphql.operation.name": { - "description": "The name of the operation being executed. Examples: * findBookById Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "subgraph.graphql.operation.type": { - "description": "The type of the operation being executed. Examples: * query * subscription * mutation Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "subgraph.name": { - "description": "The name of the subgraph Examples: * products Requirement level: Required", - "type": "boolean", - "nullable": true - } - }, - "additionalProperties": { - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": { - "type": "object", - "required": [ - "description", - "type", - "unit", - "value" - ], - "properties": { - "attributes": { - "description": "Attributes to include on the instrument.", - "type": "object", - "properties": { - "subgraph.graphql.document": { - "description": "The GraphQL document being executed. Examples: * query findBookById { bookById(id: ?) { name } } Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "subgraph.graphql.operation.name": { - "description": "The name of the operation being executed. Examples: * findBookById Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "subgraph.graphql.operation.type": { - "description": "The type of the operation being executed. Examples: * query * subscription * mutation Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "subgraph.name": { - "description": "The name of the subgraph Examples: * products Requirement level: Required", - "type": "boolean", - "nullable": true - } - }, - "additionalProperties": { - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - }, - "condition": { - "description": "The instrument conditions.", - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "description": { - "description": "The description of the instrument.", - "type": "string" - }, - "type": { - "description": "The type of instrument.", - "oneOf": [ - { - "description": "A monotonic counter https://opentelemetry.io/docs/specs/otel/metrics/data-model/#sums", - "type": "string", - "enum": [ - "counter" - ] - }, - { - "description": "A histogram https://opentelemetry.io/docs/specs/otel/metrics/data-model/#histogram", - "type": "string", - "enum": [ - "histogram" - ] - } - ] - }, - "unit": { - "description": "The units of the instrument, e.g. \"ms\", \"bytes\", \"requests\".", - "type": "string" - }, - "value": { - "description": "The value of the instrument.", - "anyOf": [ - { - "type": "string", - "enum": [ - "duration", - "unit" - ] - }, - { - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - ] - } - } - } - }, - "supergraph": { - "description": "Supergraph service instruments. For more information see documentation on Router lifecycle.", - "type": "object", - "properties": { - "cost.actual": { - "description": "A histogram of the actual cost of the operation using the currently configured cost model", - "anyOf": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "object", - "required": [ - "attributes" - ], - "properties": { - "attributes": { - "description": "Attributes for Cost", - "type": "object", - "properties": { - "cost.actual": { - "description": "The actual cost of the operation using the currently configured cost model", - "type": "boolean", - "nullable": true - }, - "cost.delta": { - "description": "The delta (estimated - actual) cost of the operation using the currently configured cost model", - "type": "boolean", - "nullable": true - }, - "cost.estimated": { - "description": "The estimated cost of the operation using the currently configured cost model", - "type": "boolean", - "nullable": true - }, - "cost.result": { - "description": "The cost result, this is an error code returned by the cost calculation or COST_OK", - "type": "boolean", - "nullable": true - }, - "graphql.document": { - "description": "The GraphQL document being executed. Examples: * query findBookById { bookById(id: ?) { name } } Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "graphql.operation.name": { - "description": "The name of the operation being executed. Examples: * findBookById Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "graphql.operation.type": { - "description": "The type of the operation being executed. Examples: * query * subscription * mutation Requirement level: Recommended", - "type": "boolean", - "nullable": true - } - }, - "additionalProperties": { - "anyOf": [ - { - "type": "object", - "required": [ - "operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "operation_name": { - "description": "The operation name from the query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "operation_kind" - ], - "properties": { - "operation_kind": { - "description": "The operation kind from the query (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "query": { - "description": "The graphql query.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "query_variable": { - "description": "The name of a graphql query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "response_header": { - "description": "The name of the response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Cost attributes", - "type": "object", - "required": [ - "cost" - ], - "properties": { - "cost": { - "description": "The cost value to select, one of: estimated, actual, delta.", - "oneOf": [ - { - "description": "The estimated cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "estimated" - ] - }, - { - "description": "The actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "actual" - ] - }, - { - "description": "The delta between the estimated and actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "delta" - ] - }, - { - "description": "The result of the cost calculation. This is the error code returned by the cost calculation.", - "type": "string", - "enum": [ - "result" - ] - } - ] - } - }, - "additionalProperties": false - } - ] - } - } - }, - "additionalProperties": false - } - ] - }, - "cost.delta": { - "description": "A histogram of the delta between the estimated and actual cost of the operation using the currently configured cost model", - "anyOf": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "object", - "required": [ - "attributes" - ], - "properties": { - "attributes": { - "description": "Attributes for Cost", - "type": "object", - "properties": { - "cost.actual": { - "description": "The actual cost of the operation using the currently configured cost model", - "type": "boolean", - "nullable": true - }, - "cost.delta": { - "description": "The delta (estimated - actual) cost of the operation using the currently configured cost model", - "type": "boolean", - "nullable": true - }, - "cost.estimated": { - "description": "The estimated cost of the operation using the currently configured cost model", - "type": "boolean", - "nullable": true - }, - "cost.result": { - "description": "The cost result, this is an error code returned by the cost calculation or COST_OK", - "type": "boolean", - "nullable": true - }, - "graphql.document": { - "description": "The GraphQL document being executed. Examples: * query findBookById { bookById(id: ?) { name } } Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "graphql.operation.name": { - "description": "The name of the operation being executed. Examples: * findBookById Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "graphql.operation.type": { - "description": "The type of the operation being executed. Examples: * query * subscription * mutation Requirement level: Recommended", - "type": "boolean", - "nullable": true - } - }, - "additionalProperties": { - "anyOf": [ - { - "type": "object", - "required": [ - "operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "operation_name": { - "description": "The operation name from the query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "operation_kind" - ], - "properties": { - "operation_kind": { - "description": "The operation kind from the query (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "query": { - "description": "The graphql query.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "query_variable": { - "description": "The name of a graphql query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "response_header": { - "description": "The name of the response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Cost attributes", - "type": "object", - "required": [ - "cost" - ], - "properties": { - "cost": { - "description": "The cost value to select, one of: estimated, actual, delta.", - "oneOf": [ - { - "description": "The estimated cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "estimated" - ] - }, - { - "description": "The actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "actual" - ] - }, - { - "description": "The delta between the estimated and actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "delta" - ] - }, - { - "description": "The result of the cost calculation. This is the error code returned by the cost calculation.", - "type": "string", - "enum": [ - "result" - ] - } - ] - } - }, - "additionalProperties": false - } - ] - } - } - }, - "additionalProperties": false - } - ] - }, - "cost.estimated": { - "description": "A histogram of the estimated cost of the operation using the currently configured cost model", - "anyOf": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "object", - "required": [ - "attributes" - ], - "properties": { - "attributes": { - "description": "Attributes for Cost", - "type": "object", - "properties": { - "cost.actual": { - "description": "The actual cost of the operation using the currently configured cost model", - "type": "boolean", - "nullable": true - }, - "cost.delta": { - "description": "The delta (estimated - actual) cost of the operation using the currently configured cost model", - "type": "boolean", - "nullable": true - }, - "cost.estimated": { - "description": "The estimated cost of the operation using the currently configured cost model", - "type": "boolean", - "nullable": true - }, - "cost.result": { - "description": "The cost result, this is an error code returned by the cost calculation or COST_OK", - "type": "boolean", - "nullable": true - }, - "graphql.document": { - "description": "The GraphQL document being executed. Examples: * query findBookById { bookById(id: ?) { name } } Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "graphql.operation.name": { - "description": "The name of the operation being executed. Examples: * findBookById Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "graphql.operation.type": { - "description": "The type of the operation being executed. Examples: * query * subscription * mutation Requirement level: Recommended", - "type": "boolean", - "nullable": true - } - }, - "additionalProperties": { - "anyOf": [ - { - "type": "object", - "required": [ - "operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "operation_name": { - "description": "The operation name from the query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "operation_kind" - ], - "properties": { - "operation_kind": { - "description": "The operation kind from the query (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "query": { - "description": "The graphql query.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "query_variable": { - "description": "The name of a graphql query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "response_header": { - "description": "The name of the response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Cost attributes", - "type": "object", - "required": [ - "cost" - ], - "properties": { - "cost": { - "description": "The cost value to select, one of: estimated, actual, delta.", - "oneOf": [ - { - "description": "The estimated cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "estimated" - ] - }, - { - "description": "The actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "actual" - ] - }, - { - "description": "The delta between the estimated and actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "delta" - ] - }, - { - "description": "The result of the cost calculation. This is the error code returned by the cost calculation.", - "type": "string", - "enum": [ - "result" - ] - } - ] - } - }, - "additionalProperties": false - } - ] - } - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": { - "type": "object", - "required": [ - "description", - "type", - "unit", - "value" - ], - "properties": { - "attributes": { - "description": "Attributes to include on the instrument.", - "type": "object", - "properties": { - "cost.actual": { - "description": "The actual cost of the operation using the currently configured cost model", - "type": "boolean", - "nullable": true - }, - "cost.delta": { - "description": "The delta (estimated - actual) cost of the operation using the currently configured cost model", - "type": "boolean", - "nullable": true - }, - "cost.estimated": { - "description": "The estimated cost of the operation using the currently configured cost model", - "type": "boolean", - "nullable": true - }, - "cost.result": { - "description": "The cost result, this is an error code returned by the cost calculation or COST_OK", - "type": "boolean", - "nullable": true - }, - "graphql.document": { - "description": "The GraphQL document being executed. Examples: * query findBookById { bookById(id: ?) { name } } Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "graphql.operation.name": { - "description": "The name of the operation being executed. Examples: * findBookById Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "graphql.operation.type": { - "description": "The type of the operation being executed. Examples: * query * subscription * mutation Requirement level: Recommended", - "type": "boolean", - "nullable": true - } - }, - "additionalProperties": { - "anyOf": [ - { - "type": "object", - "required": [ - "operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "operation_name": { - "description": "The operation name from the query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "operation_kind" - ], - "properties": { - "operation_kind": { - "description": "The operation kind from the query (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "query": { - "description": "The graphql query.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "query_variable": { - "description": "The name of a graphql query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "response_header": { - "description": "The name of the response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Cost attributes", - "type": "object", - "required": [ - "cost" - ], - "properties": { - "cost": { - "description": "The cost value to select, one of: estimated, actual, delta.", - "oneOf": [ - { - "description": "The estimated cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "estimated" - ] - }, - { - "description": "The actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "actual" - ] - }, - { - "description": "The delta between the estimated and actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "delta" - ] - }, - { - "description": "The result of the cost calculation. This is the error code returned by the cost calculation.", - "type": "string", - "enum": [ - "result" - ] - } - ] - } - }, - "additionalProperties": false - } - ] - } - }, - "condition": { - "description": "The instrument conditions.", - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "type": "object", - "required": [ - "operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "operation_name": { - "description": "The operation name from the query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "operation_kind" - ], - "properties": { - "operation_kind": { - "description": "The operation kind from the query (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "query": { - "description": "The graphql query.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "query_variable": { - "description": "The name of a graphql query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "response_header": { - "description": "The name of the response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Cost attributes", - "type": "object", - "required": [ - "cost" - ], - "properties": { - "cost": { - "description": "The cost value to select, one of: estimated, actual, delta.", - "oneOf": [ - { - "description": "The estimated cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "estimated" - ] - }, - { - "description": "The actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "actual" - ] - }, - { - "description": "The delta between the estimated and actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "delta" - ] - }, - { - "description": "The result of the cost calculation. This is the error code returned by the cost calculation.", - "type": "string", - "enum": [ - "result" - ] - } - ] - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "type": "object", - "required": [ - "operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "operation_name": { - "description": "The operation name from the query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "operation_kind" - ], - "properties": { - "operation_kind": { - "description": "The operation kind from the query (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "query": { - "description": "The graphql query.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "query_variable": { - "description": "The name of a graphql query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "response_header": { - "description": "The name of the response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Cost attributes", - "type": "object", - "required": [ - "cost" - ], - "properties": { - "cost": { - "description": "The cost value to select, one of: estimated, actual, delta.", - "oneOf": [ - { - "description": "The estimated cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "estimated" - ] - }, - { - "description": "The actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "actual" - ] - }, - { - "description": "The delta between the estimated and actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "delta" - ] - }, - { - "description": "The result of the cost calculation. This is the error code returned by the cost calculation.", - "type": "string", - "enum": [ - "result" - ] - } - ] - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SupergraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SupergraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_SupergraphSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "description": { - "description": "The description of the instrument.", - "type": "string" - }, - "type": { - "description": "The type of instrument.", - "oneOf": [ - { - "description": "A monotonic counter https://opentelemetry.io/docs/specs/otel/metrics/data-model/#sums", - "type": "string", - "enum": [ - "counter" - ] - }, - { - "description": "A histogram https://opentelemetry.io/docs/specs/otel/metrics/data-model/#histogram", - "type": "string", - "enum": [ - "histogram" - ] - } - ] - }, - "unit": { - "description": "The units of the instrument, e.g. \"ms\", \"bytes\", \"requests\".", - "type": "string" - }, - "value": { - "description": "The value of the instrument.", - "anyOf": [ - { - "type": "string", - "enum": [ - "duration", - "unit" - ] - }, - { - "anyOf": [ - { - "type": "object", - "required": [ - "operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "operation_name": { - "description": "The operation name from the query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "operation_kind" - ], - "properties": { - "operation_kind": { - "description": "The operation kind from the query (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "query": { - "description": "The graphql query.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "query_variable": { - "description": "The name of a graphql query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "response_header": { - "description": "The name of the response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Cost attributes", - "type": "object", - "required": [ - "cost" - ], - "properties": { - "cost": { - "description": "The cost value to select, one of: estimated, actual, delta.", - "oneOf": [ - { - "description": "The estimated cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "estimated" - ] - }, - { - "description": "The actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "actual" - ] - }, - { - "description": "The delta between the estimated and actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "delta" - ] - }, - { - "description": "The result of the cost calculation. This is the error code returned by the cost calculation.", - "type": "string", - "enum": [ - "result" - ] - } - ] - } - }, - "additionalProperties": false - } - ] - } - ] - } - } - } - } - }, - "additionalProperties": false - }, - "spans": { - "description": "Span configuration", - "type": "object", - "properties": { - "default_attribute_requirement_level": { - "description": "The attributes to include by default in spans based on their level as specified in the otel semantic conventions and Apollo documentation.", - "oneOf": [ - { - "description": "No default attributes set on spans, you have to set it one by one in the configuration to enable some attributes", - "type": "string", - "enum": [ - "none" - ] - }, - { - "description": "Attributes that are marked as required in otel semantic conventions and apollo documentation will be included (default)", - "type": "string", - "enum": [ - "required" - ] - }, - { - "description": "Attributes that are marked as required or recommended in otel semantic conventions and apollo documentation will be included", - "type": "string", - "enum": [ - "recommended" - ] - } - ] - }, - "mode": { - "description": "Use new OpenTelemetry spec compliant span attributes or preserve existing. This will be defaulted in future to `spec_compliant`, eventually removed in future.", - "oneOf": [ - { - "description": "Keep the request span as root span and deprecated attributes. This option will eventually removed.", - "type": "string", - "enum": [ - "deprecated" - ] - }, - { - "description": "Use new OpenTelemetry spec compliant span attributes or preserve existing. This will be the default in future.", - "type": "string", - "enum": [ - "spec_compliant" - ] - } - ] - }, - "router": { - "description": "Configuration of router spans. Log events inherit attributes from the containing span, so attributes configured here will be included on log events for a request. Router spans contain http request and response information and therefore contain http specific attributes.", - "type": "object", - "properties": { - "attributes": { - "description": "Custom attributes that are attached to the router span.", - "type": "object", - "properties": { - "baggage": { - "description": "All key values from trace baggage.", - "type": "boolean", - "nullable": true - }, - "dd.trace_id": { - "description": "The datadog trace ID. This can be output in logs and used to correlate traces in Datadog.", - "type": "boolean", - "nullable": true - }, - "error.type": { - "description": "Describes a class of error the operation ended with. Examples: * timeout * name_resolution_error * 500 Requirement level: Conditionally Required: If request has ended with an error.", - "type": "boolean", - "nullable": true - }, - "http.request.body.size": { - "description": "The size of the request payload body in bytes. This is the number of bytes transferred excluding headers and is often, but not always, present as the Content-Length header. For requests using transport encoding, this should be the compressed size. Examples: * 3495 Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "http.request.method": { - "description": "HTTP request method. Examples: * GET * POST * HEAD Requirement level: Required", - "type": "boolean", - "nullable": true - }, - "http.response.body.size": { - "description": "The size of the response payload body in bytes. This is the number of bytes transferred excluding headers and is often, but not always, present as the Content-Length header. For requests using transport encoding, this should be the compressed size. Examples: * 3495 Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "http.response.status_code": { - "description": "HTTP response status code. Examples: * 200 Requirement level: Conditionally Required: If and only if one was received/sent.", - "type": "boolean", - "nullable": true - }, - "http.route": { - "description": "The matched route (path template in the format used by the respective server framework). Examples: * /graphql Requirement level: Conditionally Required: If and only if it’s available", - "type": "boolean", - "nullable": true - }, - "network.local.address": { - "description": "Local socket address. Useful in case of a multi-IP host. Examples: * 10.1.2.80 * /tmp/my.sock Requirement level: Opt-In", - "type": "boolean", - "nullable": true - }, - "network.local.port": { - "description": "Local socket port. Useful in case of a multi-port host. Examples: * 65123 Requirement level: Opt-In", - "type": "boolean", - "nullable": true - }, - "network.peer.address": { - "description": "Peer address of the network connection - IP address or Unix domain socket name. Examples: * 10.1.2.80 * /tmp/my.sock Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "network.peer.port": { - "description": "Peer port number of the network connection. Examples: * 65123 Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "network.protocol.name": { - "description": "OSI application layer or non-OSI equivalent. Examples: * http * spdy Requirement level: Recommended: if not default (http).", - "type": "boolean", - "nullable": true - }, - "network.protocol.version": { - "description": "Version of the protocol specified in network.protocol.name. Examples: * 1.0 * 1.1 * 2 * 3 Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "network.transport": { - "description": "OSI transport layer. Examples: * tcp * udp Requirement level: Conditionally Required", - "type": "boolean", - "nullable": true - }, - "network.type": { - "description": "OSI network layer or non-OSI equivalent. Examples: * ipv4 * ipv6 Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "server.address": { - "description": "Name of the local HTTP server that received the request. Examples: * example.com * 10.1.2.80 * /tmp/my.sock Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "server.port": { - "description": "Port of the local HTTP server that received the request. Examples: * 80 * 8080 * 443 Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "trace_id": { - "description": "The OpenTelemetry trace ID. This can be output in logs.", - "type": "boolean", - "nullable": true - }, - "url.path": { - "description": "The URI path component Examples: * /search Requirement level: Required", - "type": "boolean", - "nullable": true - }, - "url.query": { - "description": "The URI query component Examples: * q=OpenTelemetry Requirement level: Conditionally Required: If and only if one was received/sent.", - "type": "boolean", - "nullable": true - }, - "url.scheme": { - "description": "The URI scheme component identifying the used protocol. Examples: * http * https Requirement level: Required", - "type": "boolean", - "nullable": true - }, - "user_agent.original": { - "description": "Value of the HTTP User-Agent header sent by the client. Examples: * CERN-LineMode/2.15 * libwww/2.17b3 Requirement level: Recommended", - "type": "boolean", - "nullable": true - } - }, - "additionalProperties": { - "anyOf": [ - { - "description": "A header from the request", - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "condition": { - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "description": "A header from the request", - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "The request method.", - "type": "object", - "required": [ - "request_method" - ], - "properties": { - "request_method": { - "description": "The request method enabled or not", - "type": "boolean" - } - }, - "additionalProperties": false - }, - { - "description": "A header from the response", - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "The trace ID of the request.", - "type": "object", - "required": [ - "trace_id" - ], - "properties": { - "trace_id": { - "description": "The format of the trace ID.", - "oneOf": [ - { - "description": "Open Telemetry trace ID, a hex string.", - "type": "string", - "enum": [ - "open_telemetry" - ] - }, - { - "description": "Datadog trace ID, a u64.", - "type": "string", - "enum": [ - "datadog" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "A value from context.", - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A value from baggage.", - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "description": "A value from an environment variable.", - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "on_graphql_error" - ], - "properties": { - "on_graphql_error": { - "description": "Boolean set to true if the response body contains graphql error", - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "description": "A header from the request", - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "The request method.", - "type": "object", - "required": [ - "request_method" - ], - "properties": { - "request_method": { - "description": "The request method enabled or not", - "type": "boolean" - } - }, - "additionalProperties": false - }, - { - "description": "A header from the response", - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "The trace ID of the request.", - "type": "object", - "required": [ - "trace_id" - ], - "properties": { - "trace_id": { - "description": "The format of the trace ID.", - "oneOf": [ - { - "description": "Open Telemetry trace ID, a hex string.", - "type": "string", - "enum": [ - "open_telemetry" - ] - }, - { - "description": "Datadog trace ID, a u64.", - "type": "string", - "enum": [ - "datadog" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "A value from context.", - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A value from baggage.", - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "description": "A value from an environment variable.", - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "on_graphql_error" - ], - "properties": { - "on_graphql_error": { - "description": "Boolean set to true if the response body contains graphql error", - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_RouterSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_RouterSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_RouterSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "The request method.", - "type": "object", - "required": [ - "request_method" - ], - "properties": { - "condition": { - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "description": "A header from the request", - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "The request method.", - "type": "object", - "required": [ - "request_method" - ], - "properties": { - "request_method": { - "description": "The request method enabled or not", - "type": "boolean" - } - }, - "additionalProperties": false - }, - { - "description": "A header from the response", - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "The trace ID of the request.", - "type": "object", - "required": [ - "trace_id" - ], - "properties": { - "trace_id": { - "description": "The format of the trace ID.", - "oneOf": [ - { - "description": "Open Telemetry trace ID, a hex string.", - "type": "string", - "enum": [ - "open_telemetry" - ] - }, - { - "description": "Datadog trace ID, a u64.", - "type": "string", - "enum": [ - "datadog" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "A value from context.", - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A value from baggage.", - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "description": "A value from an environment variable.", - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "on_graphql_error" - ], - "properties": { - "on_graphql_error": { - "description": "Boolean set to true if the response body contains graphql error", - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "description": "A header from the request", - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "The request method.", - "type": "object", - "required": [ - "request_method" - ], - "properties": { - "request_method": { - "description": "The request method enabled or not", - "type": "boolean" - } - }, - "additionalProperties": false - }, - { - "description": "A header from the response", - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "The trace ID of the request.", - "type": "object", - "required": [ - "trace_id" - ], - "properties": { - "trace_id": { - "description": "The format of the trace ID.", - "oneOf": [ - { - "description": "Open Telemetry trace ID, a hex string.", - "type": "string", - "enum": [ - "open_telemetry" - ] - }, - { - "description": "Datadog trace ID, a u64.", - "type": "string", - "enum": [ - "datadog" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "A value from context.", - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A value from baggage.", - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "description": "A value from an environment variable.", - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "on_graphql_error" - ], - "properties": { - "on_graphql_error": { - "description": "Boolean set to true if the response body contains graphql error", - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_RouterSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_RouterSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_RouterSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "request_method": { - "description": "The request method enabled or not", - "type": "boolean" - } - }, - "additionalProperties": false - }, - { - "description": "A header from the response", - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "condition": { - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "description": "A header from the request", - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "The request method.", - "type": "object", - "required": [ - "request_method" - ], - "properties": { - "request_method": { - "description": "The request method enabled or not", - "type": "boolean" - } - }, - "additionalProperties": false - }, - { - "description": "A header from the response", - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "The trace ID of the request.", - "type": "object", - "required": [ - "trace_id" - ], - "properties": { - "trace_id": { - "description": "The format of the trace ID.", - "oneOf": [ - { - "description": "Open Telemetry trace ID, a hex string.", - "type": "string", - "enum": [ - "open_telemetry" - ] - }, - { - "description": "Datadog trace ID, a u64.", - "type": "string", - "enum": [ - "datadog" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "A value from context.", - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A value from baggage.", - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "description": "A value from an environment variable.", - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "on_graphql_error" - ], - "properties": { - "on_graphql_error": { - "description": "Boolean set to true if the response body contains graphql error", - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "description": "A header from the request", - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "The request method.", - "type": "object", - "required": [ - "request_method" - ], - "properties": { - "request_method": { - "description": "The request method enabled or not", - "type": "boolean" - } - }, - "additionalProperties": false - }, - { - "description": "A header from the response", - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "The trace ID of the request.", - "type": "object", - "required": [ - "trace_id" - ], - "properties": { - "trace_id": { - "description": "The format of the trace ID.", - "oneOf": [ - { - "description": "Open Telemetry trace ID, a hex string.", - "type": "string", - "enum": [ - "open_telemetry" - ] - }, - { - "description": "Datadog trace ID, a u64.", - "type": "string", - "enum": [ - "datadog" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "A value from context.", - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A value from baggage.", - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "description": "A value from an environment variable.", - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "on_graphql_error" - ], - "properties": { - "on_graphql_error": { - "description": "Boolean set to true if the response body contains graphql error", - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_RouterSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_RouterSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_RouterSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "condition": { - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "description": "A header from the request", - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "The request method.", - "type": "object", - "required": [ - "request_method" - ], - "properties": { - "request_method": { - "description": "The request method enabled or not", - "type": "boolean" - } - }, - "additionalProperties": false - }, - { - "description": "A header from the response", - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "The trace ID of the request.", - "type": "object", - "required": [ - "trace_id" - ], - "properties": { - "trace_id": { - "description": "The format of the trace ID.", - "oneOf": [ - { - "description": "Open Telemetry trace ID, a hex string.", - "type": "string", - "enum": [ - "open_telemetry" - ] - }, - { - "description": "Datadog trace ID, a u64.", - "type": "string", - "enum": [ - "datadog" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "A value from context.", - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A value from baggage.", - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "description": "A value from an environment variable.", - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "on_graphql_error" - ], - "properties": { - "on_graphql_error": { - "description": "Boolean set to true if the response body contains graphql error", - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "description": "A header from the request", - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "The request method.", - "type": "object", - "required": [ - "request_method" - ], - "properties": { - "request_method": { - "description": "The request method enabled or not", - "type": "boolean" - } - }, - "additionalProperties": false - }, - { - "description": "A header from the response", - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "The trace ID of the request.", - "type": "object", - "required": [ - "trace_id" - ], - "properties": { - "trace_id": { - "description": "The format of the trace ID.", - "oneOf": [ - { - "description": "Open Telemetry trace ID, a hex string.", - "type": "string", - "enum": [ - "open_telemetry" - ] - }, - { - "description": "Datadog trace ID, a u64.", - "type": "string", - "enum": [ - "datadog" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "A value from context.", - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A value from baggage.", - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "description": "A value from an environment variable.", - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "on_graphql_error" - ], - "properties": { - "on_graphql_error": { - "description": "Boolean set to true if the response body contains graphql error", - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_RouterSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_RouterSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_RouterSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "The trace ID of the request.", - "type": "object", - "required": [ - "trace_id" - ], - "properties": { - "condition": { - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "description": "A header from the request", - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "The request method.", - "type": "object", - "required": [ - "request_method" - ], - "properties": { - "request_method": { - "description": "The request method enabled or not", - "type": "boolean" - } - }, - "additionalProperties": false - }, - { - "description": "A header from the response", - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "The trace ID of the request.", - "type": "object", - "required": [ - "trace_id" - ], - "properties": { - "trace_id": { - "description": "The format of the trace ID.", - "oneOf": [ - { - "description": "Open Telemetry trace ID, a hex string.", - "type": "string", - "enum": [ - "open_telemetry" - ] - }, - { - "description": "Datadog trace ID, a u64.", - "type": "string", - "enum": [ - "datadog" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "A value from context.", - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A value from baggage.", - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "description": "A value from an environment variable.", - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "on_graphql_error" - ], - "properties": { - "on_graphql_error": { - "description": "Boolean set to true if the response body contains graphql error", - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "description": "A header from the request", - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "The request method.", - "type": "object", - "required": [ - "request_method" - ], - "properties": { - "request_method": { - "description": "The request method enabled or not", - "type": "boolean" - } - }, - "additionalProperties": false - }, - { - "description": "A header from the response", - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "The trace ID of the request.", - "type": "object", - "required": [ - "trace_id" - ], - "properties": { - "trace_id": { - "description": "The format of the trace ID.", - "oneOf": [ - { - "description": "Open Telemetry trace ID, a hex string.", - "type": "string", - "enum": [ - "open_telemetry" - ] - }, - { - "description": "Datadog trace ID, a u64.", - "type": "string", - "enum": [ - "datadog" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "A value from context.", - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A value from baggage.", - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "description": "A value from an environment variable.", - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "on_graphql_error" - ], - "properties": { - "on_graphql_error": { - "description": "Boolean set to true if the response body contains graphql error", - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_RouterSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_RouterSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_RouterSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "trace_id": { - "description": "The format of the trace ID.", - "oneOf": [ - { - "description": "Open Telemetry trace ID, a hex string.", - "type": "string", - "enum": [ - "open_telemetry" - ] - }, - { - "description": "Datadog trace ID, a u64.", - "type": "string", - "enum": [ - "datadog" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "A value from context.", - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "condition": { - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "description": "A header from the request", - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "The request method.", - "type": "object", - "required": [ - "request_method" - ], - "properties": { - "request_method": { - "description": "The request method enabled or not", - "type": "boolean" - } - }, - "additionalProperties": false - }, - { - "description": "A header from the response", - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "The trace ID of the request.", - "type": "object", - "required": [ - "trace_id" - ], - "properties": { - "trace_id": { - "description": "The format of the trace ID.", - "oneOf": [ - { - "description": "Open Telemetry trace ID, a hex string.", - "type": "string", - "enum": [ - "open_telemetry" - ] - }, - { - "description": "Datadog trace ID, a u64.", - "type": "string", - "enum": [ - "datadog" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "A value from context.", - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A value from baggage.", - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "description": "A value from an environment variable.", - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "on_graphql_error" - ], - "properties": { - "on_graphql_error": { - "description": "Boolean set to true if the response body contains graphql error", - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "description": "A header from the request", - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "The request method.", - "type": "object", - "required": [ - "request_method" - ], - "properties": { - "request_method": { - "description": "The request method enabled or not", - "type": "boolean" - } - }, - "additionalProperties": false - }, - { - "description": "A header from the response", - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "The trace ID of the request.", - "type": "object", - "required": [ - "trace_id" - ], - "properties": { - "trace_id": { - "description": "The format of the trace ID.", - "oneOf": [ - { - "description": "Open Telemetry trace ID, a hex string.", - "type": "string", - "enum": [ - "open_telemetry" - ] - }, - { - "description": "Datadog trace ID, a u64.", - "type": "string", - "enum": [ - "datadog" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "A value from context.", - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A value from baggage.", - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "description": "A value from an environment variable.", - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "on_graphql_error" - ], - "properties": { - "on_graphql_error": { - "description": "Boolean set to true if the response body contains graphql error", - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_RouterSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_RouterSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_RouterSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A value from baggage.", - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "condition": { - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "description": "A header from the request", - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "The request method.", - "type": "object", - "required": [ - "request_method" - ], - "properties": { - "request_method": { - "description": "The request method enabled or not", - "type": "boolean" - } - }, - "additionalProperties": false - }, - { - "description": "A header from the response", - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "The trace ID of the request.", - "type": "object", - "required": [ - "trace_id" - ], - "properties": { - "trace_id": { - "description": "The format of the trace ID.", - "oneOf": [ - { - "description": "Open Telemetry trace ID, a hex string.", - "type": "string", - "enum": [ - "open_telemetry" - ] - }, - { - "description": "Datadog trace ID, a u64.", - "type": "string", - "enum": [ - "datadog" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "A value from context.", - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A value from baggage.", - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "description": "A value from an environment variable.", - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "on_graphql_error" - ], - "properties": { - "on_graphql_error": { - "description": "Boolean set to true if the response body contains graphql error", - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "description": "A header from the request", - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "The request method.", - "type": "object", - "required": [ - "request_method" - ], - "properties": { - "request_method": { - "description": "The request method enabled or not", - "type": "boolean" - } - }, - "additionalProperties": false - }, - { - "description": "A header from the response", - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "The trace ID of the request.", - "type": "object", - "required": [ - "trace_id" - ], - "properties": { - "trace_id": { - "description": "The format of the trace ID.", - "oneOf": [ - { - "description": "Open Telemetry trace ID, a hex string.", - "type": "string", - "enum": [ - "open_telemetry" - ] - }, - { - "description": "Datadog trace ID, a u64.", - "type": "string", - "enum": [ - "datadog" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "A value from context.", - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A value from baggage.", - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "description": "A value from an environment variable.", - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "on_graphql_error" - ], - "properties": { - "on_graphql_error": { - "description": "Boolean set to true if the response body contains graphql error", - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_RouterSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_RouterSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_RouterSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "description": "A value from an environment variable.", - "type": "object", - "required": [ - "env" - ], - "properties": { - "condition": { - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "description": "A header from the request", - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "The request method.", - "type": "object", - "required": [ - "request_method" - ], - "properties": { - "request_method": { - "description": "The request method enabled or not", - "type": "boolean" - } - }, - "additionalProperties": false - }, - { - "description": "A header from the response", - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "The trace ID of the request.", - "type": "object", - "required": [ - "trace_id" - ], - "properties": { - "trace_id": { - "description": "The format of the trace ID.", - "oneOf": [ - { - "description": "Open Telemetry trace ID, a hex string.", - "type": "string", - "enum": [ - "open_telemetry" - ] - }, - { - "description": "Datadog trace ID, a u64.", - "type": "string", - "enum": [ - "datadog" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "A value from context.", - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A value from baggage.", - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "description": "A value from an environment variable.", - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "on_graphql_error" - ], - "properties": { - "on_graphql_error": { - "description": "Boolean set to true if the response body contains graphql error", - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "description": "A header from the request", - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "The request method.", - "type": "object", - "required": [ - "request_method" - ], - "properties": { - "request_method": { - "description": "The request method enabled or not", - "type": "boolean" - } - }, - "additionalProperties": false - }, - { - "description": "A header from the response", - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "The trace ID of the request.", - "type": "object", - "required": [ - "trace_id" - ], - "properties": { - "trace_id": { - "description": "The format of the trace ID.", - "oneOf": [ - { - "description": "Open Telemetry trace ID, a hex string.", - "type": "string", - "enum": [ - "open_telemetry" - ] - }, - { - "description": "Datadog trace ID, a u64.", - "type": "string", - "enum": [ - "datadog" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "A value from context.", - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A value from baggage.", - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "description": "A value from an environment variable.", - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "on_graphql_error" - ], - "properties": { - "on_graphql_error": { - "description": "Boolean set to true if the response body contains graphql error", - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_RouterSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_RouterSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_RouterSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "condition": { - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "description": "A header from the request", - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "The request method.", - "type": "object", - "required": [ - "request_method" - ], - "properties": { - "request_method": { - "description": "The request method enabled or not", - "type": "boolean" - } - }, - "additionalProperties": false - }, - { - "description": "A header from the response", - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "The trace ID of the request.", - "type": "object", - "required": [ - "trace_id" - ], - "properties": { - "trace_id": { - "description": "The format of the trace ID.", - "oneOf": [ - { - "description": "Open Telemetry trace ID, a hex string.", - "type": "string", - "enum": [ - "open_telemetry" - ] - }, - { - "description": "Datadog trace ID, a u64.", - "type": "string", - "enum": [ - "datadog" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "A value from context.", - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A value from baggage.", - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "description": "A value from an environment variable.", - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "on_graphql_error" - ], - "properties": { - "on_graphql_error": { - "description": "Boolean set to true if the response body contains graphql error", - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "description": "A header from the request", - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "The request method.", - "type": "object", - "required": [ - "request_method" - ], - "properties": { - "request_method": { - "description": "The request method enabled or not", - "type": "boolean" - } - }, - "additionalProperties": false - }, - { - "description": "A header from the response", - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "The trace ID of the request.", - "type": "object", - "required": [ - "trace_id" - ], - "properties": { - "trace_id": { - "description": "The format of the trace ID.", - "oneOf": [ - { - "description": "Open Telemetry trace ID, a hex string.", - "type": "string", - "enum": [ - "open_telemetry" - ] - }, - { - "description": "Datadog trace ID, a u64.", - "type": "string", - "enum": [ - "datadog" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "A value from context.", - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A value from baggage.", - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "description": "A value from an environment variable.", - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "on_graphql_error" - ], - "properties": { - "on_graphql_error": { - "description": "Boolean set to true if the response body contains graphql error", - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_RouterSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_RouterSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_RouterSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "on_graphql_error" - ], - "properties": { - "condition": { - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "description": "A header from the request", - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "The request method.", - "type": "object", - "required": [ - "request_method" - ], - "properties": { - "request_method": { - "description": "The request method enabled or not", - "type": "boolean" - } - }, - "additionalProperties": false - }, - { - "description": "A header from the response", - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "The trace ID of the request.", - "type": "object", - "required": [ - "trace_id" - ], - "properties": { - "trace_id": { - "description": "The format of the trace ID.", - "oneOf": [ - { - "description": "Open Telemetry trace ID, a hex string.", - "type": "string", - "enum": [ - "open_telemetry" - ] - }, - { - "description": "Datadog trace ID, a u64.", - "type": "string", - "enum": [ - "datadog" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "A value from context.", - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A value from baggage.", - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "description": "A value from an environment variable.", - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "on_graphql_error" - ], - "properties": { - "on_graphql_error": { - "description": "Boolean set to true if the response body contains graphql error", - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "description": "A header from the request", - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "The request method.", - "type": "object", - "required": [ - "request_method" - ], - "properties": { - "request_method": { - "description": "The request method enabled or not", - "type": "boolean" - } - }, - "additionalProperties": false - }, - { - "description": "A header from the response", - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "The trace ID of the request.", - "type": "object", - "required": [ - "trace_id" - ], - "properties": { - "trace_id": { - "description": "The format of the trace ID.", - "oneOf": [ - { - "description": "Open Telemetry trace ID, a hex string.", - "type": "string", - "enum": [ - "open_telemetry" - ] - }, - { - "description": "Datadog trace ID, a u64.", - "type": "string", - "enum": [ - "datadog" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "A value from context.", - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A value from baggage.", - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "description": "A value from an environment variable.", - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "on_graphql_error" - ], - "properties": { - "on_graphql_error": { - "description": "Boolean set to true if the response body contains graphql error", - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_RouterSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_RouterSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_RouterSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "on_graphql_error": { - "description": "Boolean set to true if the response body contains graphql error", - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - } - } - }, - "additionalProperties": false - }, - "subgraph": { - "description": "Attributes to include on the subgraph span. Subgraph spans contain information about the subgraph request and response and therefore contain subgraph specific attributes.", - "type": "object", - "properties": { - "attributes": { - "description": "Custom attributes that are attached to the subgraph span.", - "type": "object", - "properties": { - "subgraph.graphql.document": { - "description": "The GraphQL document being executed. Examples: * query findBookById { bookById(id: ?) { name } } Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "subgraph.graphql.operation.name": { - "description": "The name of the operation being executed. Examples: * findBookById Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "subgraph.graphql.operation.type": { - "description": "The type of the operation being executed. Examples: * query * subscription * mutation Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "subgraph.name": { - "description": "The name of the subgraph Examples: * products Requirement level: Required", - "type": "boolean", - "nullable": true - } - }, - "additionalProperties": { - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "condition": { - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "condition": { - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "condition": { - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "condition": { - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "condition": { - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "condition": { - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "condition": { - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "condition": { - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "condition": { - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "condition": { - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "condition": { - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "condition": { - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "condition": { - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "condition": { - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "condition": { - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "condition": { - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "condition": { - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "condition": { - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "condition": { - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "condition": { - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - } - }, - "additionalProperties": false - }, - "supergraph": { - "description": "Configuration of supergraph spans. Supergraph spans contain information about the graphql request and response and therefore contain graphql specific attributes.", - "type": "object", - "properties": { - "attributes": { - "description": "Custom attributes that are attached to the supergraph span.", - "type": "object", - "properties": { - "cost.actual": { - "description": "The actual cost of the operation using the currently configured cost model", - "type": "boolean", - "nullable": true - }, - "cost.delta": { - "description": "The delta (estimated - actual) cost of the operation using the currently configured cost model", - "type": "boolean", - "nullable": true - }, - "cost.estimated": { - "description": "The estimated cost of the operation using the currently configured cost model", - "type": "boolean", - "nullable": true - }, - "cost.result": { - "description": "The cost result, this is an error code returned by the cost calculation or COST_OK", - "type": "boolean", - "nullable": true - }, - "graphql.document": { - "description": "The GraphQL document being executed. Examples: * query findBookById { bookById(id: ?) { name } } Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "graphql.operation.name": { - "description": "The name of the operation being executed. Examples: * findBookById Requirement level: Recommended", - "type": "boolean", - "nullable": true - }, - "graphql.operation.type": { - "description": "The type of the operation being executed. Examples: * query * subscription * mutation Requirement level: Recommended", - "type": "boolean", - "nullable": true - } - }, - "additionalProperties": { - "anyOf": [ - { - "type": "object", - "required": [ - "operation_name" - ], - "properties": { - "condition": { - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "type": "object", - "required": [ - "operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "operation_name": { - "description": "The operation name from the query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "operation_kind" - ], - "properties": { - "operation_kind": { - "description": "The operation kind from the query (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "query": { - "description": "The graphql query.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "query_variable": { - "description": "The name of a graphql query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "response_header": { - "description": "The name of the response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Cost attributes", - "type": "object", - "required": [ - "cost" - ], - "properties": { - "cost": { - "description": "The cost value to select, one of: estimated, actual, delta.", - "oneOf": [ - { - "description": "The estimated cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "estimated" - ] - }, - { - "description": "The actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "actual" - ] - }, - { - "description": "The delta between the estimated and actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "delta" - ] - }, - { - "description": "The result of the cost calculation. This is the error code returned by the cost calculation.", - "type": "string", - "enum": [ - "result" - ] - } - ] - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "type": "object", - "required": [ - "operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "operation_name": { - "description": "The operation name from the query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "operation_kind" - ], - "properties": { - "operation_kind": { - "description": "The operation kind from the query (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "query": { - "description": "The graphql query.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "query_variable": { - "description": "The name of a graphql query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "response_header": { - "description": "The name of the response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Cost attributes", - "type": "object", - "required": [ - "cost" - ], - "properties": { - "cost": { - "description": "The cost value to select, one of: estimated, actual, delta.", - "oneOf": [ - { - "description": "The estimated cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "estimated" - ] - }, - { - "description": "The actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "actual" - ] - }, - { - "description": "The delta between the estimated and actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "delta" - ] - }, - { - "description": "The result of the cost calculation. This is the error code returned by the cost calculation.", - "type": "string", - "enum": [ - "result" - ] - } - ] - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SupergraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SupergraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_SupergraphSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "operation_name": { - "description": "The operation name from the query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "operation_kind" - ], - "properties": { - "condition": { - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "type": "object", - "required": [ - "operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "operation_name": { - "description": "The operation name from the query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "operation_kind" - ], - "properties": { - "operation_kind": { - "description": "The operation kind from the query (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "query": { - "description": "The graphql query.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "query_variable": { - "description": "The name of a graphql query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "response_header": { - "description": "The name of the response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Cost attributes", - "type": "object", - "required": [ - "cost" - ], - "properties": { - "cost": { - "description": "The cost value to select, one of: estimated, actual, delta.", - "oneOf": [ - { - "description": "The estimated cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "estimated" - ] - }, - { - "description": "The actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "actual" - ] - }, - { - "description": "The delta between the estimated and actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "delta" - ] - }, - { - "description": "The result of the cost calculation. This is the error code returned by the cost calculation.", - "type": "string", - "enum": [ - "result" - ] - } - ] - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "type": "object", - "required": [ - "operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "operation_name": { - "description": "The operation name from the query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "operation_kind" - ], - "properties": { - "operation_kind": { - "description": "The operation kind from the query (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "query": { - "description": "The graphql query.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "query_variable": { - "description": "The name of a graphql query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "response_header": { - "description": "The name of the response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Cost attributes", - "type": "object", - "required": [ - "cost" - ], - "properties": { - "cost": { - "description": "The cost value to select, one of: estimated, actual, delta.", - "oneOf": [ - { - "description": "The estimated cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "estimated" - ] - }, - { - "description": "The actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "actual" - ] - }, - { - "description": "The delta between the estimated and actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "delta" - ] - }, - { - "description": "The result of the cost calculation. This is the error code returned by the cost calculation.", - "type": "string", - "enum": [ - "result" - ] - } - ] - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SupergraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SupergraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_SupergraphSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "operation_kind": { - "description": "The operation kind from the query (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query" - ], - "properties": { - "condition": { - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "type": "object", - "required": [ - "operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "operation_name": { - "description": "The operation name from the query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "operation_kind" - ], - "properties": { - "operation_kind": { - "description": "The operation kind from the query (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "query": { - "description": "The graphql query.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "query_variable": { - "description": "The name of a graphql query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "response_header": { - "description": "The name of the response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Cost attributes", - "type": "object", - "required": [ - "cost" - ], - "properties": { - "cost": { - "description": "The cost value to select, one of: estimated, actual, delta.", - "oneOf": [ - { - "description": "The estimated cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "estimated" - ] - }, - { - "description": "The actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "actual" - ] - }, - { - "description": "The delta between the estimated and actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "delta" - ] - }, - { - "description": "The result of the cost calculation. This is the error code returned by the cost calculation.", - "type": "string", - "enum": [ - "result" - ] - } - ] - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "type": "object", - "required": [ - "operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "operation_name": { - "description": "The operation name from the query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "operation_kind" - ], - "properties": { - "operation_kind": { - "description": "The operation kind from the query (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "query": { - "description": "The graphql query.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "query_variable": { - "description": "The name of a graphql query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "response_header": { - "description": "The name of the response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Cost attributes", - "type": "object", - "required": [ - "cost" - ], - "properties": { - "cost": { - "description": "The cost value to select, one of: estimated, actual, delta.", - "oneOf": [ - { - "description": "The estimated cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "estimated" - ] - }, - { - "description": "The actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "actual" - ] - }, - { - "description": "The delta between the estimated and actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "delta" - ] - }, - { - "description": "The result of the cost calculation. This is the error code returned by the cost calculation.", - "type": "string", - "enum": [ - "result" - ] - } - ] - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SupergraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SupergraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_SupergraphSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "query": { - "description": "The graphql query.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query_variable" - ], - "properties": { - "condition": { - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "type": "object", - "required": [ - "operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "operation_name": { - "description": "The operation name from the query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "operation_kind" - ], - "properties": { - "operation_kind": { - "description": "The operation kind from the query (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "query": { - "description": "The graphql query.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "query_variable": { - "description": "The name of a graphql query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "response_header": { - "description": "The name of the response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Cost attributes", - "type": "object", - "required": [ - "cost" - ], - "properties": { - "cost": { - "description": "The cost value to select, one of: estimated, actual, delta.", - "oneOf": [ - { - "description": "The estimated cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "estimated" - ] - }, - { - "description": "The actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "actual" - ] - }, - { - "description": "The delta between the estimated and actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "delta" - ] - }, - { - "description": "The result of the cost calculation. This is the error code returned by the cost calculation.", - "type": "string", - "enum": [ - "result" - ] - } - ] - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "type": "object", - "required": [ - "operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "operation_name": { - "description": "The operation name from the query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "operation_kind" - ], - "properties": { - "operation_kind": { - "description": "The operation kind from the query (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "query": { - "description": "The graphql query.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "query_variable": { - "description": "The name of a graphql query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "response_header": { - "description": "The name of the response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Cost attributes", - "type": "object", - "required": [ - "cost" - ], - "properties": { - "cost": { - "description": "The cost value to select, one of: estimated, actual, delta.", - "oneOf": [ - { - "description": "The estimated cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "estimated" - ] - }, - { - "description": "The actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "actual" - ] - }, - { - "description": "The delta between the estimated and actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "delta" - ] - }, - { - "description": "The result of the cost calculation. This is the error code returned by the cost calculation.", - "type": "string", - "enum": [ - "result" - ] - } - ] - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SupergraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SupergraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_SupergraphSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "query_variable": { - "description": "The name of a graphql query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "condition": { - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "type": "object", - "required": [ - "operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "operation_name": { - "description": "The operation name from the query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "operation_kind" - ], - "properties": { - "operation_kind": { - "description": "The operation kind from the query (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "query": { - "description": "The graphql query.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "query_variable": { - "description": "The name of a graphql query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "response_header": { - "description": "The name of the response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Cost attributes", - "type": "object", - "required": [ - "cost" - ], - "properties": { - "cost": { - "description": "The cost value to select, one of: estimated, actual, delta.", - "oneOf": [ - { - "description": "The estimated cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "estimated" - ] - }, - { - "description": "The actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "actual" - ] - }, - { - "description": "The delta between the estimated and actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "delta" - ] - }, - { - "description": "The result of the cost calculation. This is the error code returned by the cost calculation.", - "type": "string", - "enum": [ - "result" - ] - } - ] - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "type": "object", - "required": [ - "operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "operation_name": { - "description": "The operation name from the query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "operation_kind" - ], - "properties": { - "operation_kind": { - "description": "The operation kind from the query (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "query": { - "description": "The graphql query.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "query_variable": { - "description": "The name of a graphql query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "response_header": { - "description": "The name of the response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Cost attributes", - "type": "object", - "required": [ - "cost" - ], - "properties": { - "cost": { - "description": "The cost value to select, one of: estimated, actual, delta.", - "oneOf": [ - { - "description": "The estimated cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "estimated" - ] - }, - { - "description": "The actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "actual" - ] - }, - { - "description": "The delta between the estimated and actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "delta" - ] - }, - { - "description": "The result of the cost calculation. This is the error code returned by the cost calculation.", - "type": "string", - "enum": [ - "result" - ] - } - ] - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SupergraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SupergraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_SupergraphSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "condition": { - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "type": "object", - "required": [ - "operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "operation_name": { - "description": "The operation name from the query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "operation_kind" - ], - "properties": { - "operation_kind": { - "description": "The operation kind from the query (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "query": { - "description": "The graphql query.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "query_variable": { - "description": "The name of a graphql query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "response_header": { - "description": "The name of the response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Cost attributes", - "type": "object", - "required": [ - "cost" - ], - "properties": { - "cost": { - "description": "The cost value to select, one of: estimated, actual, delta.", - "oneOf": [ - { - "description": "The estimated cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "estimated" - ] - }, - { - "description": "The actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "actual" - ] - }, - { - "description": "The delta between the estimated and actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "delta" - ] - }, - { - "description": "The result of the cost calculation. This is the error code returned by the cost calculation.", - "type": "string", - "enum": [ - "result" - ] - } - ] - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "type": "object", - "required": [ - "operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "operation_name": { - "description": "The operation name from the query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "operation_kind" - ], - "properties": { - "operation_kind": { - "description": "The operation kind from the query (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "query": { - "description": "The graphql query.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "query_variable": { - "description": "The name of a graphql query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "response_header": { - "description": "The name of the response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Cost attributes", - "type": "object", - "required": [ - "cost" - ], - "properties": { - "cost": { - "description": "The cost value to select, one of: estimated, actual, delta.", - "oneOf": [ - { - "description": "The estimated cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "estimated" - ] - }, - { - "description": "The actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "actual" - ] - }, - { - "description": "The delta between the estimated and actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "delta" - ] - }, - { - "description": "The result of the cost calculation. This is the error code returned by the cost calculation.", - "type": "string", - "enum": [ - "result" - ] - } - ] - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SupergraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SupergraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_SupergraphSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "response_header": { - "description": "The name of the response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "condition": { - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "type": "object", - "required": [ - "operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "operation_name": { - "description": "The operation name from the query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "operation_kind" - ], - "properties": { - "operation_kind": { - "description": "The operation kind from the query (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "query": { - "description": "The graphql query.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "query_variable": { - "description": "The name of a graphql query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "response_header": { - "description": "The name of the response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Cost attributes", - "type": "object", - "required": [ - "cost" - ], - "properties": { - "cost": { - "description": "The cost value to select, one of: estimated, actual, delta.", - "oneOf": [ - { - "description": "The estimated cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "estimated" - ] - }, - { - "description": "The actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "actual" - ] - }, - { - "description": "The delta between the estimated and actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "delta" - ] - }, - { - "description": "The result of the cost calculation. This is the error code returned by the cost calculation.", - "type": "string", - "enum": [ - "result" - ] - } - ] - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "type": "object", - "required": [ - "operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "operation_name": { - "description": "The operation name from the query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "operation_kind" - ], - "properties": { - "operation_kind": { - "description": "The operation kind from the query (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "query": { - "description": "The graphql query.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "query_variable": { - "description": "The name of a graphql query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "response_header": { - "description": "The name of the response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Cost attributes", - "type": "object", - "required": [ - "cost" - ], - "properties": { - "cost": { - "description": "The cost value to select, one of: estimated, actual, delta.", - "oneOf": [ - { - "description": "The estimated cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "estimated" - ] - }, - { - "description": "The actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "actual" - ] - }, - { - "description": "The delta between the estimated and actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "delta" - ] - }, - { - "description": "The result of the cost calculation. This is the error code returned by the cost calculation.", - "type": "string", - "enum": [ - "result" - ] - } - ] - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SupergraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SupergraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_SupergraphSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "condition": { - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "type": "object", - "required": [ - "operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "operation_name": { - "description": "The operation name from the query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "operation_kind" - ], - "properties": { - "operation_kind": { - "description": "The operation kind from the query (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "query": { - "description": "The graphql query.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "query_variable": { - "description": "The name of a graphql query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "response_header": { - "description": "The name of the response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Cost attributes", - "type": "object", - "required": [ - "cost" - ], - "properties": { - "cost": { - "description": "The cost value to select, one of: estimated, actual, delta.", - "oneOf": [ - { - "description": "The estimated cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "estimated" - ] - }, - { - "description": "The actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "actual" - ] - }, - { - "description": "The delta between the estimated and actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "delta" - ] - }, - { - "description": "The result of the cost calculation. This is the error code returned by the cost calculation.", - "type": "string", - "enum": [ - "result" - ] - } - ] - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "type": "object", - "required": [ - "operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "operation_name": { - "description": "The operation name from the query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "operation_kind" - ], - "properties": { - "operation_kind": { - "description": "The operation kind from the query (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "query": { - "description": "The graphql query.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "query_variable": { - "description": "The name of a graphql query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "response_header": { - "description": "The name of the response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Cost attributes", - "type": "object", - "required": [ - "cost" - ], - "properties": { - "cost": { - "description": "The cost value to select, one of: estimated, actual, delta.", - "oneOf": [ - { - "description": "The estimated cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "estimated" - ] - }, - { - "description": "The actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "actual" - ] - }, - { - "description": "The delta between the estimated and actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "delta" - ] - }, - { - "description": "The result of the cost calculation. This is the error code returned by the cost calculation.", - "type": "string", - "enum": [ - "result" - ] - } - ] - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SupergraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SupergraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_SupergraphSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "condition": { - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "type": "object", - "required": [ - "operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "operation_name": { - "description": "The operation name from the query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "operation_kind" - ], - "properties": { - "operation_kind": { - "description": "The operation kind from the query (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "query": { - "description": "The graphql query.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "query_variable": { - "description": "The name of a graphql query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "response_header": { - "description": "The name of the response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Cost attributes", - "type": "object", - "required": [ - "cost" - ], - "properties": { - "cost": { - "description": "The cost value to select, one of: estimated, actual, delta.", - "oneOf": [ - { - "description": "The estimated cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "estimated" - ] - }, - { - "description": "The actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "actual" - ] - }, - { - "description": "The delta between the estimated and actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "delta" - ] - }, - { - "description": "The result of the cost calculation. This is the error code returned by the cost calculation.", - "type": "string", - "enum": [ - "result" - ] - } - ] - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "type": "object", - "required": [ - "operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "operation_name": { - "description": "The operation name from the query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "operation_kind" - ], - "properties": { - "operation_kind": { - "description": "The operation kind from the query (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "query": { - "description": "The graphql query.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "query_variable": { - "description": "The name of a graphql query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "response_header": { - "description": "The name of the response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Cost attributes", - "type": "object", - "required": [ - "cost" - ], - "properties": { - "cost": { - "description": "The cost value to select, one of: estimated, actual, delta.", - "oneOf": [ - { - "description": "The estimated cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "estimated" - ] - }, - { - "description": "The actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "actual" - ] - }, - { - "description": "The delta between the estimated and actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "delta" - ] - }, - { - "description": "The result of the cost calculation. This is the error code returned by the cost calculation.", - "type": "string", - "enum": [ - "result" - ] - } - ] - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SupergraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SupergraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_SupergraphSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "condition": { - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "type": "object", - "required": [ - "operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "operation_name": { - "description": "The operation name from the query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "operation_kind" - ], - "properties": { - "operation_kind": { - "description": "The operation kind from the query (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "query": { - "description": "The graphql query.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "query_variable": { - "description": "The name of a graphql query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "response_header": { - "description": "The name of the response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Cost attributes", - "type": "object", - "required": [ - "cost" - ], - "properties": { - "cost": { - "description": "The cost value to select, one of: estimated, actual, delta.", - "oneOf": [ - { - "description": "The estimated cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "estimated" - ] - }, - { - "description": "The actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "actual" - ] - }, - { - "description": "The delta between the estimated and actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "delta" - ] - }, - { - "description": "The result of the cost calculation. This is the error code returned by the cost calculation.", - "type": "string", - "enum": [ - "result" - ] - } - ] - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "type": "object", - "required": [ - "operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "operation_name": { - "description": "The operation name from the query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "operation_kind" - ], - "properties": { - "operation_kind": { - "description": "The operation kind from the query (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "query": { - "description": "The graphql query.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "query_variable": { - "description": "The name of a graphql query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "response_header": { - "description": "The name of the response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Cost attributes", - "type": "object", - "required": [ - "cost" - ], - "properties": { - "cost": { - "description": "The cost value to select, one of: estimated, actual, delta.", - "oneOf": [ - { - "description": "The estimated cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "estimated" - ] - }, - { - "description": "The actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "actual" - ] - }, - { - "description": "The delta between the estimated and actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "delta" - ] - }, - { - "description": "The result of the cost calculation. This is the error code returned by the cost calculation.", - "type": "string", - "enum": [ - "result" - ] - } - ] - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SupergraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SupergraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_SupergraphSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "condition": { - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "type": "object", - "required": [ - "operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "operation_name": { - "description": "The operation name from the query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "operation_kind" - ], - "properties": { - "operation_kind": { - "description": "The operation kind from the query (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "query": { - "description": "The graphql query.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "query_variable": { - "description": "The name of a graphql query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "response_header": { - "description": "The name of the response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Cost attributes", - "type": "object", - "required": [ - "cost" - ], - "properties": { - "cost": { - "description": "The cost value to select, one of: estimated, actual, delta.", - "oneOf": [ - { - "description": "The estimated cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "estimated" - ] - }, - { - "description": "The actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "actual" - ] - }, - { - "description": "The delta between the estimated and actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "delta" - ] - }, - { - "description": "The result of the cost calculation. This is the error code returned by the cost calculation.", - "type": "string", - "enum": [ - "result" - ] - } - ] - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "type": "object", - "required": [ - "operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "operation_name": { - "description": "The operation name from the query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "operation_kind" - ], - "properties": { - "operation_kind": { - "description": "The operation kind from the query (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "query": { - "description": "The graphql query.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "query_variable": { - "description": "The name of a graphql query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "response_header": { - "description": "The name of the response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Cost attributes", - "type": "object", - "required": [ - "cost" - ], - "properties": { - "cost": { - "description": "The cost value to select, one of: estimated, actual, delta.", - "oneOf": [ - { - "description": "The estimated cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "estimated" - ] - }, - { - "description": "The actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "actual" - ] - }, - { - "description": "The delta between the estimated and actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "delta" - ] - }, - { - "description": "The result of the cost calculation. This is the error code returned by the cost calculation.", - "type": "string", - "enum": [ - "result" - ] - } - ] - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SupergraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SupergraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_SupergraphSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "condition": { - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "type": "object", - "required": [ - "operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "operation_name": { - "description": "The operation name from the query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "operation_kind" - ], - "properties": { - "operation_kind": { - "description": "The operation kind from the query (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "query": { - "description": "The graphql query.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "query_variable": { - "description": "The name of a graphql query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "response_header": { - "description": "The name of the response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Cost attributes", - "type": "object", - "required": [ - "cost" - ], - "properties": { - "cost": { - "description": "The cost value to select, one of: estimated, actual, delta.", - "oneOf": [ - { - "description": "The estimated cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "estimated" - ] - }, - { - "description": "The actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "actual" - ] - }, - { - "description": "The delta between the estimated and actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "delta" - ] - }, - { - "description": "The result of the cost calculation. This is the error code returned by the cost calculation.", - "type": "string", - "enum": [ - "result" - ] - } - ] - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "type": "object", - "required": [ - "operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "operation_name": { - "description": "The operation name from the query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "operation_kind" - ], - "properties": { - "operation_kind": { - "description": "The operation kind from the query (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "query": { - "description": "The graphql query.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "query_variable": { - "description": "The name of a graphql query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "response_header": { - "description": "The name of the response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Cost attributes", - "type": "object", - "required": [ - "cost" - ], - "properties": { - "cost": { - "description": "The cost value to select, one of: estimated, actual, delta.", - "oneOf": [ - { - "description": "The estimated cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "estimated" - ] - }, - { - "description": "The actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "actual" - ] - }, - { - "description": "The delta between the estimated and actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "delta" - ] - }, - { - "description": "The result of the cost calculation. This is the error code returned by the cost calculation.", - "type": "string", - "enum": [ - "result" - ] - } - ] - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SupergraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SupergraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_SupergraphSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Cost attributes", - "type": "object", - "required": [ - "cost" - ], - "properties": { - "condition": { - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "type": "object", - "required": [ - "operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "operation_name": { - "description": "The operation name from the query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "operation_kind" - ], - "properties": { - "operation_kind": { - "description": "The operation kind from the query (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "query": { - "description": "The graphql query.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "query_variable": { - "description": "The name of a graphql query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "response_header": { - "description": "The name of the response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Cost attributes", - "type": "object", - "required": [ - "cost" - ], - "properties": { - "cost": { - "description": "The cost value to select, one of: estimated, actual, delta.", - "oneOf": [ - { - "description": "The estimated cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "estimated" - ] - }, - { - "description": "The actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "actual" - ] - }, - { - "description": "The delta between the estimated and actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "delta" - ] - }, - { - "description": "The result of the cost calculation. This is the error code returned by the cost calculation.", - "type": "string", - "enum": [ - "result" - ] - } - ] - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "type": "object", - "required": [ - "operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "operation_name": { - "description": "The operation name from the query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "operation_kind" - ], - "properties": { - "operation_kind": { - "description": "The operation kind from the query (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "query": { - "description": "The graphql query.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "query_variable": { - "description": "The name of a graphql query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "response_header": { - "description": "The name of the response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Cost attributes", - "type": "object", - "required": [ - "cost" - ], - "properties": { - "cost": { - "description": "The cost value to select, one of: estimated, actual, delta.", - "oneOf": [ - { - "description": "The estimated cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "estimated" - ] - }, - { - "description": "The actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "actual" - ] - }, - { - "description": "The delta between the estimated and actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "delta" - ] - }, - { - "description": "The result of the cost calculation. This is the error code returned by the cost calculation.", - "type": "string", - "enum": [ - "result" - ] - } - ] - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SupergraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SupergraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_SupergraphSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] - }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] - } - ] - }, - "cost": { - "description": "The cost value to select, one of: estimated, actual, delta.", - "oneOf": [ - { - "description": "The estimated cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "estimated" - ] - }, - { - "description": "The actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "actual" - ] - }, - { - "description": "The delta between the estimated and actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "delta" - ] - }, - { - "description": "The result of the cost calculation. This is the error code returned by the cost calculation.", - "type": "string", - "enum": [ - "result" - ] - } - ] - } - }, - "additionalProperties": false - } - ] - } - } - }, - "additionalProperties": false - } + "any": { + "items": { + "$ref": "#/definitions/Condition_for_SupergraphSelector", + "description": "#/definitions/Condition_for_SupergraphSelector" }, - "additionalProperties": false + "type": "array" + } + }, + "required": [ + "any" + ], + "type": "object" + }, + { + "additionalProperties": false, + "description": "The sub-condition must not be true", + "properties": { + "not": { + "$ref": "#/definitions/Condition_for_SupergraphSelector", + "description": "#/definitions/Condition_for_SupergraphSelector" } }, - "additionalProperties": false + "required": [ + "not" + ], + "type": "object" + }, + { + "description": "Static true condition", + "enum": [ + "true" + ], + "type": "string" + }, + { + "description": "Static false condition", + "enum": [ + "false" + ], + "type": "string" + } + ] + }, + "Conf": { + "description": "Configuration for the test plugin", + "properties": { + "name": { + "description": "The name of the test", + "type": "string" } }, - "additionalProperties": false + "required": [ + "name" + ], + "type": "object" }, - "tls": { - "description": "TLS related configuration options.", - "default": { - "supergraph": null, + "Conf2": { + "additionalProperties": false, + "description": "Authentication", + "properties": { + "router": { + "$ref": "#/definitions/RouterConf", + "description": "#/definitions/RouterConf", + "nullable": true + }, "subgraph": { - "all": { - "certificate_authorities": null, - "client_authentication": null - }, - "subgraphs": {} + "$ref": "#/definitions/Config3", + "description": "#/definitions/Config3", + "nullable": true + } + }, + "type": "object" + }, + "Conf3": { + "description": "Authorization plugin", + "properties": { + "directives": { + "$ref": "#/definitions/Directives", + "description": "#/definitions/Directives" + }, + "require_authentication": { + "default": false, + "description": "Reject unauthenticated requests", + "type": "boolean" } }, - "type": "object", + "type": "object" + }, + "Conf4": { + "additionalProperties": false, + "description": "Configures the externalization plugin", "properties": { + "execution": { + "$ref": "#/definitions/ExecutionStage", + "description": "#/definitions/ExecutionStage" + }, + "router": { + "$ref": "#/definitions/RouterStage", + "description": "#/definitions/RouterStage" + }, "subgraph": { - "description": "Configuration options pertaining to the subgraph server component.", + "$ref": "#/definitions/SubgraphStages", + "description": "#/definitions/SubgraphStages" + }, + "supergraph": { + "$ref": "#/definitions/SupergraphStage", + "description": "#/definitions/SupergraphStage" + }, + "timeout": { "default": { - "all": { - "certificate_authorities": null, - "client_authentication": null - }, - "subgraphs": {} + "nanos": 0, + "secs": 1 }, - "type": "object", - "properties": { - "all": { - "description": "options applying to all subgraphs", - "default": { - "certificate_authorities": null, - "client_authentication": null - }, - "type": "object", - "properties": { - "certificate_authorities": { - "description": "list of certificate authorities in PEM format", - "type": "string", - "nullable": true - }, - "client_authentication": { - "description": "client certificate authentication", - "type": "object", - "required": [ - "certificate_chain", - "key" - ], - "properties": { - "certificate_chain": { - "description": "list of certificates in PEM format", - "writeOnly": true, - "type": "string" - }, - "key": { - "description": "key in PEM format", - "writeOnly": true, - "type": "string" - } - }, - "additionalProperties": false, - "nullable": true - } - }, - "additionalProperties": false - }, - "subgraphs": { - "description": "per subgraph options", - "default": {}, - "type": "object", - "additionalProperties": { - "description": "Configuration options pertaining to the subgraph server component.", - "type": "object", - "properties": { - "certificate_authorities": { - "description": "list of certificate authorities in PEM format", - "type": "string", - "nullable": true - }, - "client_authentication": { - "description": "client certificate authentication", - "type": "object", - "required": [ - "certificate_chain", - "key" - ], - "properties": { - "certificate_chain": { - "description": "list of certificates in PEM format", - "writeOnly": true, - "type": "string" - }, - "key": { - "description": "key in PEM format", - "writeOnly": true, - "type": "string" - } - }, - "additionalProperties": false, - "nullable": true - } - }, - "additionalProperties": false - } - } - } + "description": "The timeout for external requests", + "type": "string" }, - "supergraph": { - "description": "TLS server configuration\n\nthis will affect the GraphQL endpoint and any other endpoint targeting the same listen address", - "type": "object", - "required": [ - "certificate", - "certificate_chain", - "key" - ], - "properties": { - "certificate": { - "description": "server certificate in PEM format", - "writeOnly": true, - "type": "string" - }, - "certificate_chain": { - "description": "list of certificate authorities in PEM format", - "writeOnly": true, - "type": "string" - }, - "key": { - "description": "server key in PEM format", - "writeOnly": true, - "type": "string" - } + "url": { + "description": "The url you'd like to offload processing to", + "type": "string" + } + }, + "required": [ + "url" + ], + "type": "object" + }, + "Conf5": { + "anyOf": [ + { + "additionalProperties": { + "type": "string" }, - "additionalProperties": false, - "nullable": true + "description": "Subgraph URL mappings", + "type": "object" + } + ], + "description": "Subgraph URL mappings" + }, + "Conf6": { + "additionalProperties": false, + "description": "Configuration for the Rhai Plugin", + "properties": { + "main": { + "description": "The main entry point for Rhai script evaluation", + "nullable": true, + "type": "string" + }, + "scripts": { + "description": "The directory where Rhai scripts can be found", + "nullable": true, + "type": "string" } }, - "additionalProperties": false + "type": "object" }, - "traffic_shaping": { - "description": "Configuration for the experimental traffic shaping plugin", - "type": "object", + "Conf7": { + "additionalProperties": false, + "description": "Telemetry configuration", "properties": { - "all": { - "description": "Applied on all subgraphs", - "type": "object", - "properties": { - "compression": { - "description": "Enable compression for subgraphs (available compressions are deflate, br, gzip)", - "oneOf": [ - { - "description": "gzip", - "type": "string", - "enum": [ - "gzip" - ] - }, - { - "description": "deflate", - "type": "string", - "enum": [ - "deflate" - ] - }, - { - "description": "brotli", - "type": "string", - "enum": [ - "br" - ] - }, - { - "description": "identity", - "type": "string", - "enum": [ - "identity" - ] - } - ], - "nullable": true - }, - "deduplicate_query": { - "description": "Enable query deduplication", - "type": "boolean", - "nullable": true - }, - "experimental_http2": { - "description": "Enable HTTP2 for subgraphs", - "oneOf": [ - { - "description": "Enable HTTP2 for subgraphs", - "type": "string", - "enum": [ - "enable" - ] - }, - { - "description": "Disable HTTP2 for subgraphs", - "type": "string", - "enum": [ - "disable" - ] - }, - { - "description": "Only HTTP2 is active", - "type": "string", - "enum": [ - "http2only" - ] - } - ], - "nullable": true - }, - "experimental_retry": { - "description": "Retry configuration", - "type": "object", - "properties": { - "min_per_sec": { - "description": "minimum rate of retries allowed to accomodate clients that have just started issuing requests, or clients that do not issue many requests per window. The default value is 10", - "type": "integer", - "format": "uint32", - "minimum": 0.0, - "nullable": true - }, - "retry_mutations": { - "description": "allows request retries on mutations. This should only be activated if mutations are idempotent. Disabled by default", - "type": "boolean", - "nullable": true - }, - "retry_percent": { - "description": "percentage of calls to deposit that can be retried. This is in addition to any retries allowed for via min_per_sec. Must be between 0 and 1000, default value is 0.2", - "type": "number", - "format": "float", - "nullable": true - }, - "ttl": { - "description": "how long a single deposit should be considered. Must be between 1 and 60 seconds, default value is 10 seconds", - "type": "string" - } - }, - "additionalProperties": false, - "nullable": true - }, - "global_rate_limit": { - "description": "Enable global rate limiting", - "type": "object", - "required": [ - "capacity", - "interval" - ], - "properties": { - "capacity": { - "description": "Number of requests allowed", - "type": "integer", - "format": "uint64", - "minimum": 1.0 - }, - "interval": { - "description": "Per interval", - "type": "string" - } - }, - "additionalProperties": false, - "nullable": true - }, - "timeout": { - "description": "Enable timeout for incoming requests", - "type": "string" - } - }, - "additionalProperties": false, - "nullable": true + "apollo": { + "$ref": "#/definitions/Config8", + "description": "#/definitions/Config8" }, - "deduplicate_variables": { - "description": "DEPRECATED, now always enabled: Enable variable deduplication optimization when sending requests to subgraphs (https://github.com/apollographql/router/issues/87)", - "type": "boolean", - "nullable": true + "exporters": { + "$ref": "#/definitions/Exporters", + "description": "#/definitions/Exporters" }, - "router": { - "description": "Applied at the router level", - "type": "object", - "properties": { - "global_rate_limit": { - "description": "Enable global rate limiting", - "type": "object", - "required": [ - "capacity", - "interval" - ], - "properties": { - "capacity": { - "description": "Number of requests allowed", - "type": "integer", - "format": "uint64", - "minimum": 1.0 - }, - "interval": { - "description": "Per interval", - "type": "string" - } - }, - "additionalProperties": false, - "nullable": true - }, - "timeout": { - "description": "Enable timeout for incoming requests", - "type": "string" - } - }, - "additionalProperties": false, - "nullable": true + "instrumentation": { + "$ref": "#/definitions/Instrumentation", + "description": "#/definitions/Instrumentation" + } + }, + "type": "object" + }, + "Config": { + "description": "This is a broken plugin for testing purposes only.", + "properties": { + "enabled": { + "description": "Enable the broken plugin.", + "type": "boolean" + } + }, + "required": [ + "enabled" + ], + "type": "object" + }, + "Config10": { + "additionalProperties": false, + "description": "Prometheus configuration", + "properties": { + "enabled": { + "default": false, + "description": "Set to true to enable", + "type": "boolean" }, - "subgraphs": { - "description": "Applied on specific subgraphs", - "type": "object", - "additionalProperties": { - "description": "Traffic shaping options", - "type": "object", - "properties": { - "compression": { - "description": "Enable compression for subgraphs (available compressions are deflate, br, gzip)", - "oneOf": [ - { - "description": "gzip", - "type": "string", - "enum": [ - "gzip" - ] - }, - { - "description": "deflate", - "type": "string", - "enum": [ - "deflate" - ] - }, - { - "description": "brotli", - "type": "string", - "enum": [ - "br" - ] - }, - { - "description": "identity", - "type": "string", - "enum": [ - "identity" - ] - } - ], - "nullable": true - }, - "deduplicate_query": { - "description": "Enable query deduplication", - "type": "boolean", - "nullable": true - }, - "experimental_http2": { - "description": "Enable HTTP2 for subgraphs", - "oneOf": [ - { - "description": "Enable HTTP2 for subgraphs", - "type": "string", - "enum": [ - "enable" - ] - }, - { - "description": "Disable HTTP2 for subgraphs", - "type": "string", - "enum": [ - "disable" - ] - }, - { - "description": "Only HTTP2 is active", - "type": "string", - "enum": [ - "http2only" - ] - } - ], - "nullable": true - }, - "experimental_retry": { - "description": "Retry configuration", - "type": "object", - "properties": { - "min_per_sec": { - "description": "minimum rate of retries allowed to accomodate clients that have just started issuing requests, or clients that do not issue many requests per window. The default value is 10", - "type": "integer", - "format": "uint32", - "minimum": 0.0, - "nullable": true - }, - "retry_mutations": { - "description": "allows request retries on mutations. This should only be activated if mutations are idempotent. Disabled by default", - "type": "boolean", - "nullable": true - }, - "retry_percent": { - "description": "percentage of calls to deposit that can be retried. This is in addition to any retries allowed for via min_per_sec. Must be between 0 and 1000, default value is 0.2", - "type": "number", - "format": "float", - "nullable": true - }, - "ttl": { - "description": "how long a single deposit should be considered. Must be between 1 and 60 seconds, default value is 10 seconds", - "type": "string" - } - }, - "additionalProperties": false, - "nullable": true - }, - "global_rate_limit": { - "description": "Enable global rate limiting", - "type": "object", - "required": [ - "capacity", - "interval" - ], - "properties": { - "capacity": { - "description": "Number of requests allowed", - "type": "integer", - "format": "uint64", - "minimum": 1.0 - }, - "interval": { - "description": "Per interval", - "type": "string" - } - }, - "additionalProperties": false, - "nullable": true - }, - "timeout": { - "description": "Enable timeout for incoming requests", - "type": "string" - } - }, - "additionalProperties": false - } + "listen": { + "$ref": "#/definitions/ListenAddr", + "description": "#/definitions/ListenAddr" + }, + "path": { + "default": "/metrics", + "description": "The path where prometheus will be exposed", + "type": "string" } }, - "additionalProperties": false - } - }, - "definitions": { - "Condition_for_RouterSelector": { - "oneOf": [ + "type": "object" + }, + "Config11": { + "anyOf": [ { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], + "additionalProperties": false, "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "description": "A header from the request", - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "The request method.", - "type": "object", - "required": [ - "request_method" - ], - "properties": { - "request_method": { - "description": "The request method enabled or not", - "type": "boolean" - } - }, - "additionalProperties": false - }, - { - "description": "A header from the response", - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "The trace ID of the request.", - "type": "object", - "required": [ - "trace_id" - ], - "properties": { - "trace_id": { - "description": "The format of the trace ID.", - "oneOf": [ - { - "description": "Open Telemetry trace ID, a hex string.", - "type": "string", - "enum": [ - "open_telemetry" - ] - }, - { - "description": "Datadog trace ID, a u64.", - "type": "string", - "enum": [ - "datadog" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "A value from context.", - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A value from baggage.", - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "description": "A value from an environment variable.", - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "on_graphql_error" - ], - "properties": { - "on_graphql_error": { - "description": "Boolean set to true if the response body contains graphql error", - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 + "agent": { + "$ref": "#/definitions/AgentConfig", + "description": "#/definitions/AgentConfig" + }, + "batch_processor": { + "$ref": "#/definitions/BatchProcessorConfig", + "description": "#/definitions/BatchProcessorConfig" + }, + "enabled": { + "description": "Enable Jaeger", + "type": "boolean" } }, - "additionalProperties": false - }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", "required": [ - "exists" + "enabled" ], - "properties": { - "exists": { - "anyOf": [ - { - "description": "A header from the request", - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "The request method.", - "type": "object", - "required": [ - "request_method" - ], - "properties": { - "request_method": { - "description": "The request method enabled or not", - "type": "boolean" - } - }, - "additionalProperties": false - }, - { - "description": "A header from the response", - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "The trace ID of the request.", - "type": "object", - "required": [ - "trace_id" - ], - "properties": { - "trace_id": { - "description": "The format of the trace ID.", - "oneOf": [ - { - "description": "Open Telemetry trace ID, a hex string.", - "type": "string", - "enum": [ - "open_telemetry" - ] - }, - { - "description": "Datadog trace ID, a u64.", - "type": "string", - "enum": [ - "datadog" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "description": "A value from context.", - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A value from baggage.", - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "description": "A value from an environment variable.", - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "on_graphql_error" - ], - "properties": { - "on_graphql_error": { - "description": "Boolean set to true if the response body contains graphql error", - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false + "type": "object" }, { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], + "additionalProperties": false, "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_RouterSelector" - } + "batch_processor": { + "$ref": "#/definitions/BatchProcessorConfig", + "description": "#/definitions/BatchProcessorConfig" + }, + "collector": { + "$ref": "#/definitions/CollectorConfig", + "description": "#/definitions/CollectorConfig" + }, + "enabled": { + "description": "Enable Jaeger", + "type": "boolean" } }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", "required": [ - "any" + "enabled" ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_RouterSelector" - } - } - }, - "additionalProperties": false + "type": "object" + } + ] + }, + "Config12": { + "additionalProperties": false, + "properties": { + "batch_processor": { + "$ref": "#/definitions/BatchProcessorConfig", + "description": "#/definitions/BatchProcessorConfig" }, - { - "description": "The sub-condition must not be true", - "type": "object", - "required": [ - "not" - ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_RouterSelector" - } - }, - "additionalProperties": false + "enabled": { + "description": "Enable zipkin", + "type": "boolean" }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] + "endpoint": { + "$ref": "#/definitions/UriEndpoint", + "description": "#/definitions/UriEndpoint" + } + }, + "required": [ + "enabled" + ], + "type": "object" + }, + "Config13": { + "additionalProperties": false, + "properties": { + "batch_processor": { + "$ref": "#/definitions/BatchProcessorConfig", + "description": "#/definitions/BatchProcessorConfig" }, - { - "description": "Static false condition", - "type": "string", - "enum": [ - "false" - ] + "enable_span_mapping": { + "default": false, + "description": "Enable datadog span mapping for span name and resource name.", + "type": "boolean" + }, + "enabled": { + "description": "Enable datadog", + "type": "boolean" + }, + "endpoint": { + "$ref": "#/definitions/UriEndpoint", + "description": "#/definitions/UriEndpoint" } - ] + }, + "required": [ + "enabled" + ], + "type": "object" }, - "Condition_for_SubgraphSelector": { - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" - ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false + "Config14": { + "additionalProperties": false, + "description": "Configuration for the experimental traffic shaping plugin", + "properties": { + "all": { + "$ref": "#/definitions/SubgraphShaping", + "description": "#/definitions/SubgraphShaping", + "nullable": true }, - { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], - "properties": { - "exists": { - "anyOf": [ - { - "type": "object", - "required": [ - "subgraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_operation_name": { - "description": "The operation name from the subgraph query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_operation_kind" - ], - "properties": { - "subgraph_operation_kind": { - "description": "The kind of the subgraph operation (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_query": { - "description": "The graphql query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_query_variable": { - "description": "The name of a subgraph query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", - "type": "object", - "required": [ - "subgraph_response_body" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_body": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_data" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_data": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_errors" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "subgraph_response_errors": { - "description": "The subgraph response body json path.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_request_header": { - "description": "The name of a subgraph request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "subgraph_response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "subgraph_response_header": { - "description": "The name of a subgraph response header.", - "type": "string" - } - }, - "additionalProperties": false + "deduplicate_variables": { + "description": "DEPRECATED, now always enabled: Enable variable deduplication optimization when sending requests to subgraphs (https://github.com/apollographql/router/issues/87)", + "nullable": true, + "type": "boolean" + }, + "router": { + "$ref": "#/definitions/RouterShaping", + "description": "#/definitions/RouterShaping", + "nullable": true + }, + "subgraphs": { + "additionalProperties": { + "$ref": "#/definitions/SubgraphShaping", + "description": "#/definitions/SubgraphShaping" + }, + "description": "Applied on specific subgraphs", + "type": "object" + } + }, + "type": "object" + }, + "Config2": { + "description": "Restricted plugin (for testing purposes only)", + "properties": { + "enabled": { + "description": "Enable the restricted plugin (for testing purposes only)", + "type": "boolean" + } + }, + "required": [ + "enabled" + ], + "type": "object" + }, + "Config3": { + "additionalProperties": false, + "description": "Configure subgraph authentication", + "properties": { + "all": { + "$ref": "#/definitions/AuthConfig", + "description": "#/definitions/AuthConfig", + "nullable": true + }, + "subgraphs": { + "additionalProperties": { + "$ref": "#/definitions/AuthConfig", + "description": "#/definitions/AuthConfig" + }, + "description": "Create a configuration that will apply only to a specific subgraph.", + "type": "object" + } + }, + "type": "object" + }, + "Config4": { + "additionalProperties": false, + "description": "Configuration for header propagation", + "properties": { + "all": { + "$ref": "#/definitions/HeadersLocation", + "description": "#/definitions/HeadersLocation", + "nullable": true + }, + "subgraphs": { + "additionalProperties": { + "$ref": "#/definitions/HeadersLocation", + "description": "#/definitions/HeadersLocation" + }, + "description": "Rules to specific subgraphs", + "type": "object" + } + }, + "type": "object" + }, + "Config5": { + "additionalProperties": false, + "description": "Configuration for exposing errors that originate from subgraphs", + "properties": { + "all": { + "default": false, + "description": "Include errors from all subgraphs", + "type": "boolean" + }, + "subgraphs": { + "additionalProperties": { + "type": "boolean" + }, + "default": {}, + "description": "Include errors from specific subgraphs", + "type": "object" + } + }, + "type": "object" + }, + "Config6": { + "additionalProperties": false, + "description": "Configuration for entity caching", + "properties": { + "enabled": { + "description": "activates caching for all subgraphs, unless overriden in subgraph specific configuration", + "nullable": true, + "type": "boolean" + }, + "metrics": { + "$ref": "#/definitions/Metrics", + "description": "#/definitions/Metrics" + }, + "redis": { + "$ref": "#/definitions/RedisCache", + "description": "#/definitions/RedisCache" + }, + "subgraphs": { + "additionalProperties": { + "$ref": "#/definitions/Subgraph", + "description": "#/definitions/Subgraph" + }, + "description": "Per subgraph configuration", + "type": "object" + } + }, + "required": [ + "redis" + ], + "type": "object" + }, + "Config7": { + "description": "Configuration for the progressive override plugin", + "type": "object" + }, + "Config8": { + "additionalProperties": false, + "properties": { + "batch_processor": { + "$ref": "#/definitions/BatchProcessorConfig", + "description": "#/definitions/BatchProcessorConfig" + }, + "buffer_size": { + "default": 10000, + "description": "The buffer size for sending traces to Apollo. Increase this if you are experiencing lost traces.", + "format": "uint", + "minimum": 1.0, + "type": "integer" + }, + "client_name_header": { + "default": "apollographql-client-name", + "description": "The name of the header to extract from requests when populating 'client nane' for traces and metrics in Apollo Studio.", + "nullable": true, + "type": "string" + }, + "client_version_header": { + "default": "apollographql-client-version", + "description": "The name of the header to extract from requests when populating 'client version' for traces and metrics in Apollo Studio.", + "nullable": true, + "type": "string" + }, + "endpoint": { + "default": "https://usage-reporting.api.apollographql.com/api/ingress/traces", + "description": "The Apollo Studio endpoint for exporting traces and metrics.", + "type": "string" + }, + "errors": { + "$ref": "#/definitions/ErrorsConfiguration", + "description": "#/definitions/ErrorsConfiguration" + }, + "experimental_otlp_endpoint": { + "default": "https://usage-reporting.api.apollographql.com/", + "description": "The Apollo Studio endpoint for exporting traces and metrics.", + "type": "string" + }, + "field_level_instrumentation_sampler": { + "$ref": "#/definitions/SamplerOption", + "description": "#/definitions/SamplerOption" + }, + "send_headers": { + "$ref": "#/definitions/ForwardHeaders", + "description": "#/definitions/ForwardHeaders" + }, + "send_variable_values": { + "$ref": "#/definitions/ForwardValues", + "description": "#/definitions/ForwardValues" + } + }, + "type": "object" + }, + "Config9": { + "additionalProperties": false, + "properties": { + "batch_processor": { + "$ref": "#/definitions/BatchProcessorConfig", + "description": "#/definitions/BatchProcessorConfig" + }, + "enabled": { + "description": "Enable otlp", + "type": "boolean" + }, + "endpoint": { + "$ref": "#/definitions/UriEndpoint", + "description": "#/definitions/UriEndpoint" + }, + "grpc": { + "$ref": "#/definitions/GrpcExporter", + "description": "#/definitions/GrpcExporter" + }, + "http": { + "$ref": "#/definitions/HttpExporter", + "description": "#/definitions/HttpExporter" + }, + "protocol": { + "$ref": "#/definitions/Protocol", + "description": "#/definitions/Protocol" + }, + "temporality": { + "$ref": "#/definitions/Temporality", + "description": "#/definitions/Temporality" + } + }, + "required": [ + "enabled" + ], + "type": "object" + }, + "ContextForward": { + "additionalProperties": false, + "description": "Configuration to forward context values in metric attributes/labels", + "properties": { + "default": { + "$ref": "#/definitions/AttributeValue", + "description": "#/definitions/AttributeValue", + "nullable": true + }, + "named": { + "description": "The name of the value in the context", + "type": "string" + }, + "rename": { + "description": "The optional output name", + "nullable": true, + "type": "string" + } + }, + "required": [ + "named" + ], + "type": "object" + }, + "Cors": { + "additionalProperties": false, + "description": "Cross origin request configuration.", + "properties": { + "allow_any_origin": { + "default": false, + "description": "Set to true to allow any origin.\n\nDefaults to false Having this set to true is the only way to allow Origin: null.", + "type": "boolean" + }, + "allow_credentials": { + "default": false, + "description": "Set to true to add the `Access-Control-Allow-Credentials` header.", + "type": "boolean" + }, + "allow_headers": { + "default": [], + "description": "The headers to allow.\n\nIf this value is not set, the router will mirror client's `Access-Control-Request-Headers`.\n\nNote that if you set headers here, you also want to have a look at your `CSRF` plugins configuration, and make sure you either: - accept `x-apollo-operation-name` AND / OR `apollo-require-preflight` - defined `csrf` required headers in your yml configuration, as shown in the `examples/cors-and-csrf/custom-headers.router.yaml` files.", + "items": { + "type": "string" + }, + "type": "array" + }, + "expose_headers": { + "description": "Which response headers should be made available to scripts running in the browser, in response to a cross-origin request.", + "items": { + "type": "string" + }, + "nullable": true, + "type": "array" + }, + "match_origins": { + "description": "`Regex`es you want to match the origins against to determine if they're allowed. Defaults to an empty list. Note that `origins` will be evaluated before `match_origins`", + "items": { + "type": "string" + }, + "nullable": true, + "type": "array" + }, + "max_age": { + "description": "The `Access-Control-Max-Age` header value in time units", + "type": "string" + }, + "methods": { + "default": [ + "GET", + "POST", + "OPTIONS" + ], + "description": "Allowed request methods. Defaults to GET, POST, OPTIONS.", + "items": { + "type": "string" + }, + "type": "array" + }, + "origins": { + "default": [ + "https://studio.apollographql.com" + ], + "description": "The origin(s) to allow requests from. Defaults to `https://studio.apollographql.com/` for Apollo Studio.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "CostValue": { + "oneOf": [ + { + "description": "The estimated cost of the operation using the currently configured cost model", + "enum": [ + "estimated" + ], + "type": "string" + }, + { + "description": "The actual cost of the operation using the currently configured cost model", + "enum": [ + "actual" + ], + "type": "string" + }, + { + "description": "The delta between the estimated and actual cost of the operation using the currently configured cost model", + "enum": [ + "delta" + ], + "type": "string" + }, + { + "description": "The result of the cost calculation. This is the error code returned by the cost calculation.", + "enum": [ + "result" + ], + "type": "string" + } + ] + }, + "DefaultAttributeRequirementLevel": { + "oneOf": [ + { + "description": "No default attributes set on spans, you have to set it one by one in the configuration to enable some attributes", + "enum": [ + "none" + ], + "type": "string" + }, + { + "description": "Attributes that are marked as required in otel semantic conventions and apollo documentation will be included (default)", + "enum": [ + "required" + ], + "type": "string" + }, + { + "description": "Attributes that are marked as required or recommended in otel semantic conventions and apollo documentation will be included", + "enum": [ + "recommended" + ], + "type": "string" + } + ] + }, + "DefaultChainConfig": { + "additionalProperties": false, + "description": "Configuration of the DefaultChainProvider", + "properties": { + "assume_role": { + "$ref": "#/definitions/AssumeRoleProvider", + "description": "#/definitions/AssumeRoleProvider", + "nullable": true + }, + "profile_name": { + "description": "The profile name used by this provider", + "nullable": true, + "type": "string" + }, + "region": { + "description": "The AWS region this chain applies to.", + "type": "string" + }, + "service_name": { + "description": "The service you're trying to access, eg: \"s3\", \"vpc-lattice-svcs\", etc.", + "type": "string" + } + }, + "required": [ + "region", + "service_name" + ], + "type": "object" + }, + "DefaultedStandardInstrument_for_ActiveRequestsAttributes": { + "anyOf": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "additionalProperties": false, + "properties": { + "attributes": { + "$ref": "#/definitions/ActiveRequestsAttributes", + "description": "#/definitions/ActiveRequestsAttributes" + } + }, + "required": [ + "attributes" + ], + "type": "object" + } + ] + }, + "DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::RouterAttributes_apollo_router::plugins::telemetry::config_new::selectors::RouterSelector": { + "anyOf": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "additionalProperties": false, + "properties": { + "attributes": { + "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::RouterAttributes_apollo_router::plugins::telemetry::config_new::selectors::RouterSelector", + "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::RouterAttributes_apollo_router::plugins::telemetry::config_new::selectors::RouterSelector" + } + }, + "required": [ + "attributes" + ], + "type": "object" + } + ] + }, + "DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SubgraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SubgraphSelector": { + "anyOf": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "additionalProperties": false, + "properties": { + "attributes": { + "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SubgraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SubgraphSelector", + "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SubgraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SubgraphSelector" + } + }, + "required": [ + "attributes" + ], + "type": "object" + } + ] + }, + "DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SupergraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SupergraphSelector": { + "anyOf": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "additionalProperties": false, + "properties": { + "attributes": { + "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SupergraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SupergraphSelector", + "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SupergraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SupergraphSelector" + } + }, + "required": [ + "attributes" + ], + "type": "object" + } + ] + }, + "DemandControlConfig": { + "additionalProperties": false, + "description": "Demand control configuration", + "properties": { + "enabled": { + "description": "Enable demand control", + "type": "boolean" + }, + "mode": { + "$ref": "#/definitions/Mode", + "description": "#/definitions/Mode" + }, + "strategy": { + "$ref": "#/definitions/StrategyConfig", + "description": "#/definitions/StrategyConfig" + } + }, + "required": [ + "enabled", + "mode", + "strategy" + ], + "type": "object" + }, + "Directives": { + "properties": { + "dry_run": { + "default": false, + "description": "generates the authorization error messages without modying the query", + "type": "boolean" + }, + "enabled": { + "default": true, + "description": "enables the `@authenticated` and `@requiresScopes` directives", + "type": "boolean" + }, + "errors": { + "$ref": "#/definitions/ErrorConfig", + "description": "#/definitions/ErrorConfig" + }, + "reject_unauthorized": { + "default": false, + "description": "refuse a query entirely if any part would be filtered", + "type": "boolean" + } + }, + "type": "object" + }, + "Disabled": { + "enum": [ + "disabled" + ], + "type": "string" + }, + "Enabled": { + "enum": [ + "enabled" + ], + "type": "string" + }, + "ErrorConfig": { + "properties": { + "log": { + "default": true, + "description": "log authorization errors", + "type": "boolean" + }, + "response": { + "$ref": "#/definitions/ErrorLocation", + "description": "#/definitions/ErrorLocation" + } + }, + "type": "object" + }, + "ErrorConfiguration": { + "additionalProperties": false, + "properties": { + "redact": { + "default": true, + "description": "Redact subgraph errors to Apollo Studio", + "type": "boolean" + }, + "send": { + "default": true, + "description": "Send subgraph errors to Apollo Studio", + "type": "boolean" + } + }, + "type": "object" + }, + "ErrorLocation": { + "oneOf": [ + { + "description": "store authorization errors in the response errors", + "enum": [ + "errors" + ], + "type": "string" + }, + { + "description": "store authorization errors in the response extensions", + "enum": [ + "extensions" + ], + "type": "string" + }, + { + "description": "do not add the authorization errors to the GraphQL response", + "enum": [ + "disabled" + ], + "type": "string" + } + ] + }, + "ErrorRepr": { + "oneOf": [ + { + "description": "The error reason", + "enum": [ + "reason" + ], + "type": "string" + } + ] + }, + "ErrorsConfiguration": { + "additionalProperties": false, + "properties": { + "subgraph": { + "$ref": "#/definitions/SubgraphErrorConfig", + "description": "#/definitions/SubgraphErrorConfig" + } + }, + "type": "object" + }, + "ErrorsForward": { + "additionalProperties": false, + "properties": { + "extensions": { + "description": "Forward extensions values as custom attributes/labels in metrics", + "items": { + "$ref": "#/definitions/BodyForward", + "description": "#/definitions/BodyForward" + }, + "type": "array" + }, + "include_messages": { + "description": "Will include the error message in a \"message\" attribute", + "nullable": true, + "type": "boolean" + } + }, + "type": "object" + }, + "EventLevel": { + "enum": [ + "info", + "warn", + "error", + "off" + ], + "type": "string" + }, + "EventOn": { + "description": "When to trigger the event.", + "oneOf": [ + { + "description": "Log the event on request", + "enum": [ + "request" + ], + "type": "string" + }, + { + "description": "Log the event on response", + "enum": [ + "response" + ], + "type": "string" + }, + { + "description": "Log the event on every chunks in the response", + "enum": [ + "event_response" + ], + "type": "string" + }, + { + "description": "Log the event on error", + "enum": [ + "error" + ], + "type": "string" + } + ] + }, + "Event_for_RouterAttributes_and_RouterSelector": { + "description": "An event that can be logged as part of a trace. The event has an implicit `type` attribute that matches the name of the event in the yaml and a message that can be used to provide additional information.", + "properties": { + "attributes": { + "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::RouterAttributes_apollo_router::plugins::telemetry::config_new::selectors::RouterSelector", + "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::RouterAttributes_apollo_router::plugins::telemetry::config_new::selectors::RouterSelector" + }, + "condition": { + "$ref": "#/definitions/Condition_for_RouterSelector", + "description": "#/definitions/Condition_for_RouterSelector" + }, + "level": { + "$ref": "#/definitions/EventLevel", + "description": "#/definitions/EventLevel" + }, + "message": { + "description": "The event message.", + "type": "string" + }, + "on": { + "$ref": "#/definitions/EventOn", + "description": "#/definitions/EventOn" + } + }, + "required": [ + "level", + "message", + "on" + ], + "type": "object" + }, + "Event_for_RouterSelector": { + "oneOf": [ + { + "description": "For every supergraph response payload (including subscription events and defer events)", + "enum": [ + "event_duration" + ], + "type": "string" + }, + { + "description": "For every supergraph response payload (including subscription events and defer events)", + "enum": [ + "event_unit" + ], + "type": "string" + }, + { + "additionalProperties": false, + "description": "For every supergraph response payload (including subscription events and defer events)", + "properties": { + "event_custom": { + "$ref": "#/definitions/RouterSelector", + "description": "#/definitions/RouterSelector" + } + }, + "required": [ + "event_custom" + ], + "type": "object" + } + ] + }, + "Event_for_SubgraphAttributes_and_SubgraphSelector": { + "description": "An event that can be logged as part of a trace. The event has an implicit `type` attribute that matches the name of the event in the yaml and a message that can be used to provide additional information.", + "properties": { + "attributes": { + "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SubgraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SubgraphSelector", + "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SubgraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SubgraphSelector" + }, + "condition": { + "$ref": "#/definitions/Condition_for_SubgraphSelector", + "description": "#/definitions/Condition_for_SubgraphSelector" + }, + "level": { + "$ref": "#/definitions/EventLevel", + "description": "#/definitions/EventLevel" + }, + "message": { + "description": "The event message.", + "type": "string" + }, + "on": { + "$ref": "#/definitions/EventOn", + "description": "#/definitions/EventOn" + } + }, + "required": [ + "level", + "message", + "on" + ], + "type": "object" + }, + "Event_for_SubgraphSelector": { + "oneOf": [ + { + "description": "For every supergraph response payload (including subscription events and defer events)", + "enum": [ + "event_duration" + ], + "type": "string" + }, + { + "description": "For every supergraph response payload (including subscription events and defer events)", + "enum": [ + "event_unit" + ], + "type": "string" + }, + { + "additionalProperties": false, + "description": "For every supergraph response payload (including subscription events and defer events)", + "properties": { + "event_custom": { + "$ref": "#/definitions/SubgraphSelector", + "description": "#/definitions/SubgraphSelector" + } + }, + "required": [ + "event_custom" + ], + "type": "object" + } + ] + }, + "Event_for_SupergraphAttributes_and_SupergraphSelector": { + "description": "An event that can be logged as part of a trace. The event has an implicit `type` attribute that matches the name of the event in the yaml and a message that can be used to provide additional information.", + "properties": { + "attributes": { + "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SupergraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SupergraphSelector", + "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SupergraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SupergraphSelector" + }, + "condition": { + "$ref": "#/definitions/Condition_for_SupergraphSelector", + "description": "#/definitions/Condition_for_SupergraphSelector" + }, + "level": { + "$ref": "#/definitions/EventLevel", + "description": "#/definitions/EventLevel" + }, + "message": { + "description": "The event message.", + "type": "string" + }, + "on": { + "$ref": "#/definitions/EventOn", + "description": "#/definitions/EventOn" + } + }, + "required": [ + "level", + "message", + "on" + ], + "type": "object" + }, + "Event_for_SupergraphSelector": { + "oneOf": [ + { + "description": "For every supergraph response payload (including subscription events and defer events)", + "enum": [ + "event_duration" + ], + "type": "string" + }, + { + "description": "For every supergraph response payload (including subscription events and defer events)", + "enum": [ + "event_unit" + ], + "type": "string" + }, + { + "additionalProperties": false, + "description": "For every supergraph response payload (including subscription events and defer events)", + "properties": { + "event_custom": { + "$ref": "#/definitions/SupergraphSelector", + "description": "#/definitions/SupergraphSelector" + } + }, + "required": [ + "event_custom" + ], + "type": "object" + } + ] + }, + "Events": { + "additionalProperties": false, + "description": "Events are", + "properties": { + "router": { + "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::events::RouterEventsConfig_apollo_router::plugins::telemetry::config_new::events::Event", + "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::events::RouterEventsConfig_apollo_router::plugins::telemetry::config_new::events::Event" + }, + "subgraph": { + "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::events::SubgraphEventsConfig_apollo_router::plugins::telemetry::config_new::events::Event", + "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::events::SubgraphEventsConfig_apollo_router::plugins::telemetry::config_new::events::Event" + }, + "supergraph": { + "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::events::SupergraphEventsConfig_apollo_router::plugins::telemetry::config_new::events::Event", + "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::events::SupergraphEventsConfig_apollo_router::plugins::telemetry::config_new::events::Event" + } + }, + "type": "object" + }, + "ExecutionRequestConf": { + "additionalProperties": false, + "description": "What information is passed to a router request/response stage", + "properties": { + "body": { + "default": false, + "description": "Send the body", + "type": "boolean" + }, + "context": { + "default": false, + "description": "Send the context", + "type": "boolean" + }, + "headers": { + "default": false, + "description": "Send the headers", + "type": "boolean" + }, + "method": { + "default": false, + "description": "Send the method", + "type": "boolean" + }, + "query_plan": { + "default": false, + "description": "Send the query plan", + "type": "boolean" + }, + "sdl": { + "default": false, + "description": "Send the SDL", + "type": "boolean" + } + }, + "type": "object" + }, + "ExecutionResponseConf": { + "additionalProperties": false, + "description": "What information is passed to a router request/response stage", + "properties": { + "body": { + "default": false, + "description": "Send the body", + "type": "boolean" + }, + "context": { + "default": false, + "description": "Send the context", + "type": "boolean" + }, + "headers": { + "default": false, + "description": "Send the headers", + "type": "boolean" + }, + "sdl": { + "default": false, + "description": "Send the SDL", + "type": "boolean" + }, + "status_code": { + "default": false, + "description": "Send the HTTP status", + "type": "boolean" + } + }, + "type": "object" + }, + "ExecutionStage": { + "properties": { + "request": { + "$ref": "#/definitions/ExecutionRequestConf", + "description": "#/definitions/ExecutionRequestConf" + }, + "response": { + "$ref": "#/definitions/ExecutionResponseConf", + "description": "#/definitions/ExecutionResponseConf" + } + }, + "type": "object" + }, + "Exporters": { + "additionalProperties": false, + "description": "Exporter configuration", + "properties": { + "logging": { + "$ref": "#/definitions/Logging", + "description": "#/definitions/Logging" + }, + "metrics": { + "$ref": "#/definitions/Metrics2", + "description": "#/definitions/Metrics2" + }, + "tracing": { + "$ref": "#/definitions/Tracing", + "description": "#/definitions/Tracing" + } + }, + "type": "object" + }, + "ExposeQueryPlanConfig": { + "description": "Expose query plan", + "type": "boolean" + }, + "ExposeTraceId": { + "additionalProperties": false, + "properties": { + "enabled": { + "default": false, + "description": "Expose the trace_id in response headers", + "type": "boolean" + }, + "format": { + "$ref": "#/definitions/TraceIdFormat", + "description": "#/definitions/TraceIdFormat" + }, + "header_name": { + "description": "Choose the header name to expose trace_id (default: apollo-trace-id)", + "nullable": true, + "type": "string" + } + }, + "type": "object" + }, + "FileUploadProtocols": { + "additionalProperties": false, + "description": "Configuration for the various protocols supported by the file upload plugin", + "properties": { + "multipart": { + "$ref": "#/definitions/MultipartRequest", + "description": "#/definitions/MultipartRequest" + } + }, + "required": [ + "multipart" + ], + "type": "object" + }, + "FileUploadsConfig": { + "additionalProperties": false, + "description": "Configuration for File Uploads plugin", + "properties": { + "enabled": { + "description": "Whether the file upload plugin should be enabled (default: false)", + "type": "boolean" + }, + "protocols": { + "$ref": "#/definitions/FileUploadProtocols", + "description": "#/definitions/FileUploadProtocols" + } + }, + "required": [ + "enabled", + "protocols" + ], + "type": "object" + }, + "ForbidMutationsConfig": { + "description": "Forbid mutations configuration", + "type": "boolean" + }, + "Forward": { + "additionalProperties": false, + "description": "Configuration to forward from headers/body", + "properties": { + "body": { + "description": "Forward body values as custom attributes/labels in metrics", + "items": { + "$ref": "#/definitions/BodyForward", + "description": "#/definitions/BodyForward" + }, + "type": "array" + }, + "header": { + "description": "Forward header values as custom attributes/labels in metrics", + "items": { + "$ref": "#/definitions/HeaderForward", + "description": "#/definitions/HeaderForward" + }, + "type": "array" + } + }, + "type": "object" + }, + "ForwardHeaders": { + "description": "Forward headers", + "oneOf": [ + { + "description": "Don't send any headers", + "enum": [ + "none" + ], + "type": "string" + }, + { + "description": "Send all headers", + "enum": [ + "all" + ], + "type": "string" + }, + { + "additionalProperties": false, + "description": "Send only the headers specified", + "properties": { + "only": { + "description": "Send only the headers specified", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "only" + ], + "type": "object" + }, + { + "additionalProperties": false, + "description": "Send all headers except those specified", + "properties": { + "except": { + "description": "Send all headers except those specified", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "except" + ], + "type": "object" + } + ] + }, + "ForwardValues": { + "description": "Forward GraphQL variables", + "oneOf": [ + { + "description": "Dont send any variables", + "enum": [ + "none" + ], + "type": "string" + }, + { + "description": "Send all variables", + "enum": [ + "all" + ], + "type": "string" + }, + { + "additionalProperties": false, + "description": "Send only the variables specified", + "properties": { + "only": { + "description": "Send only the variables specified", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "only" + ], + "type": "object" + }, + { + "additionalProperties": false, + "description": "Send all variables except those specified", + "properties": { + "except": { + "description": "Send all variables except those specified", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "except" + ], + "type": "object" + } + ] + }, + "GrpcExporter": { + "additionalProperties": false, + "properties": { + "ca": { + "description": "The optional certificate authority (CA) certificate to be used in TLS configuration.", + "nullable": true, + "type": "string" + }, + "cert": { + "description": "The optional cert for tls config", + "nullable": true, + "type": "string" + }, + "domain_name": { + "description": "The optional domain name for tls config. Note that domain name is will be defaulted to match the endpoint is not explicitly set.", + "nullable": true, + "type": "string" + }, + "key": { + "description": "The optional private key file for TLS configuration.", + "nullable": true, + "type": "string" + }, + "metadata": { + "additionalProperties": true, + "default": {}, + "description": "gRPC metadata", + "type": "object" + } + }, + "type": "object" + }, + "Header": { + "additionalProperties": false, + "description": "Insert a header", + "properties": { + "name": { + "description": "The name of the header", + "type": "string" + }, + "value": { + "description": "The value for the header", + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "HeaderForward": { + "anyOf": [ + { + "additionalProperties": false, + "description": "Match via header name", + "properties": { + "default": { + "$ref": "#/definitions/AttributeValue", + "description": "#/definitions/AttributeValue", + "nullable": true + }, + "named": { + "description": "The name of the header", + "type": "string" + }, + "rename": { + "description": "The optional output name", + "nullable": true, + "type": "string" + } + }, + "required": [ + "named" + ], + "type": "object" + }, + { + "additionalProperties": false, + "description": "Match via rgex", + "properties": { + "matching": { + "description": "Using a regex on the header name", + "type": "string" + } + }, + "required": [ + "matching" + ], + "type": "object" + } + ], + "description": "Configuration to forward header values in metric labels" + }, + "HeaderLoggingCondition": { + "anyOf": [ + { + "additionalProperties": false, + "description": "Match header value given a regex to display logs", + "properties": { + "body": { + "default": false, + "description": "Display request/response body (default: false)", + "type": "boolean" + }, + "headers": { + "default": false, + "description": "Display request/response headers (default: false)", + "type": "boolean" + }, + "match": { + "description": "Regex to match the header value", + "type": "string" + }, + "name": { + "description": "Header name", + "type": "string" + } + }, + "required": [ + "match", + "name" + ], + "type": "object" + }, + { + "additionalProperties": false, + "description": "Match header value given a value to display logs", + "properties": { + "body": { + "default": false, + "description": "Display request/response body (default: false)", + "type": "boolean" + }, + "headers": { + "default": false, + "description": "Display request/response headers (default: false)", + "type": "boolean" + }, + "name": { + "description": "Header name", + "type": "string" + }, + "value": { + "description": "Header value", + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + } + ] + }, + "HeadersLocation": { + "additionalProperties": false, + "properties": { + "request": { + "description": "Propagate/Insert/Remove headers from request", + "items": { + "$ref": "#/definitions/Operation", + "description": "#/definitions/Operation" + }, + "type": "array" + } + }, + "required": [ + "request" + ], + "type": "object" + }, + "HealthCheck": { + "additionalProperties": false, + "description": "Configuration options pertaining to the http server component.", + "properties": { + "enabled": { + "default": true, + "description": "Set to false to disable the health check", + "type": "boolean" + }, + "listen": { + "$ref": "#/definitions/ListenAddr", + "description": "#/definitions/ListenAddr" + }, + "path": { + "default": "/health", + "description": "Optionally set a custom healthcheck path Defaults to /health", + "type": "string" + } + }, + "type": "object" + }, + "HeartbeatInterval": { + "anyOf": [ + { + "$ref": "#/definitions/Disabled", + "description": "#/definitions/Disabled" + }, + { + "$ref": "#/definitions/Enabled", + "description": "#/definitions/Enabled" + }, + { + "description": "enable with custom interval, e.g. '100ms', '10s' or '1m'", + "type": "string" + } + ] + }, + "Homepage": { + "additionalProperties": false, + "description": "Configuration options pertaining to the home page.", + "properties": { + "enabled": { + "default": true, + "description": "Set to false to disable the homepage", + "type": "boolean" + }, + "graph_ref": { + "description": "Graph reference This will allow you to redirect from the Apollo Router landing page back to Apollo Studio Explorer", + "nullable": true, + "type": "string" + } + }, + "type": "object" + }, + "Http2Config": { + "oneOf": [ + { + "description": "Enable HTTP2 for subgraphs", + "enum": [ + "enable" + ], + "type": "string" + }, + { + "description": "Disable HTTP2 for subgraphs", + "enum": [ + "disable" + ], + "type": "string" + }, + { + "description": "Only HTTP2 is active", + "enum": [ + "http2only" + ], + "type": "string" + } + ] + }, + "HttpExporter": { + "additionalProperties": false, + "properties": { + "headers": { + "additionalProperties": { + "type": "string" + }, + "default": {}, + "description": "Headers to send on report requests", + "type": "object" + } + }, + "type": "object" + }, + "InMemoryCache": { + "additionalProperties": false, + "description": "In memory cache configuration", + "properties": { + "limit": { + "description": "Number of entries in the Least Recently Used cache", + "format": "uint", + "minimum": 1.0, + "type": "integer" + } + }, + "required": [ + "limit" + ], + "type": "object" + }, + "Insert": { + "anyOf": [ + { + "$ref": "#/definitions/InsertStatic", + "description": "#/definitions/InsertStatic" + }, + { + "$ref": "#/definitions/InsertFromContext", + "description": "#/definitions/InsertFromContext" + }, + { + "$ref": "#/definitions/InsertFromBody", + "description": "#/definitions/InsertFromBody" + } + ], + "description": "Insert header" + }, + "Insert2": { + "additionalProperties": false, + "description": "Configuration to insert custom attributes/labels in metrics", + "properties": { + "name": { + "description": "The name of the attribute to insert", + "type": "string" + }, + "value": { + "$ref": "#/definitions/AttributeValue", + "description": "#/definitions/AttributeValue" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "InsertFromBody": { + "additionalProperties": false, + "description": "Insert header with a value coming from body", + "properties": { + "default": { + "description": "The default if the path in the body did not resolve to an element", + "nullable": true, + "type": "string" + }, + "name": { + "description": "The target header name", + "type": "string" + }, + "path": { + "description": "The path in the request body", + "type": "string" + } + }, + "required": [ + "name", + "path" + ], + "type": "object" + }, + "InsertFromContext": { + "additionalProperties": false, + "description": "Insert header with a value coming from context key", + "properties": { + "from_context": { + "description": "Specify context key to fetch value", + "type": "string" + }, + "name": { + "description": "Specify header name", + "type": "string" + } + }, + "required": [ + "from_context", + "name" + ], + "type": "object" + }, + "InsertStatic": { + "additionalProperties": false, + "description": "Insert static header", + "properties": { + "name": { + "description": "The name of the header", + "type": "string" + }, + "value": { + "description": "The value for the header", + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "InstrumentType": { + "oneOf": [ + { + "description": "A monotonic counter https://opentelemetry.io/docs/specs/otel/metrics/data-model/#sums", + "enum": [ + "counter" + ], + "type": "string" + }, + { + "description": "A histogram https://opentelemetry.io/docs/specs/otel/metrics/data-model/#histogram", + "enum": [ + "histogram" + ], + "type": "string" + } + ] + }, + "InstrumentValue_for_RouterSelector": { + "anyOf": [ + { + "$ref": "#/definitions/Standard", + "description": "#/definitions/Standard" + }, + { + "$ref": "#/definitions/Event_for_RouterSelector", + "description": "#/definitions/Event_for_RouterSelector" + }, + { + "$ref": "#/definitions/RouterSelector", + "description": "#/definitions/RouterSelector" + } + ] + }, + "InstrumentValue_for_SubgraphSelector": { + "anyOf": [ + { + "$ref": "#/definitions/Standard", + "description": "#/definitions/Standard" + }, + { + "$ref": "#/definitions/Event_for_SubgraphSelector", + "description": "#/definitions/Event_for_SubgraphSelector" + }, + { + "$ref": "#/definitions/SubgraphSelector", + "description": "#/definitions/SubgraphSelector" + } + ] + }, + "InstrumentValue_for_SupergraphSelector": { + "anyOf": [ + { + "$ref": "#/definitions/Standard", + "description": "#/definitions/Standard" + }, + { + "$ref": "#/definitions/Event_for_SupergraphSelector", + "description": "#/definitions/Event_for_SupergraphSelector" + }, + { + "$ref": "#/definitions/SupergraphSelector", + "description": "#/definitions/SupergraphSelector" + } + ] + }, + "Instrument_for_RouterAttributes_and_RouterSelector": { + "additionalProperties": false, + "properties": { + "attributes": { + "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::RouterAttributes_apollo_router::plugins::telemetry::config_new::selectors::RouterSelector", + "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::RouterAttributes_apollo_router::plugins::telemetry::config_new::selectors::RouterSelector" + }, + "condition": { + "$ref": "#/definitions/Condition_for_RouterSelector", + "description": "#/definitions/Condition_for_RouterSelector" + }, + "description": { + "description": "The description of the instrument.", + "type": "string" + }, + "type": { + "$ref": "#/definitions/InstrumentType", + "description": "#/definitions/InstrumentType" + }, + "unit": { + "description": "The units of the instrument, e.g. \"ms\", \"bytes\", \"requests\".", + "type": "string" + }, + "value": { + "$ref": "#/definitions/InstrumentValue_for_RouterSelector", + "description": "#/definitions/InstrumentValue_for_RouterSelector" + } + }, + "required": [ + "description", + "type", + "unit", + "value" + ], + "type": "object" + }, + "Instrument_for_SubgraphAttributes_and_SubgraphSelector": { + "additionalProperties": false, + "properties": { + "attributes": { + "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SubgraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SubgraphSelector", + "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SubgraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SubgraphSelector" + }, + "condition": { + "$ref": "#/definitions/Condition_for_SubgraphSelector", + "description": "#/definitions/Condition_for_SubgraphSelector" + }, + "description": { + "description": "The description of the instrument.", + "type": "string" + }, + "type": { + "$ref": "#/definitions/InstrumentType", + "description": "#/definitions/InstrumentType" + }, + "unit": { + "description": "The units of the instrument, e.g. \"ms\", \"bytes\", \"requests\".", + "type": "string" + }, + "value": { + "$ref": "#/definitions/InstrumentValue_for_SubgraphSelector", + "description": "#/definitions/InstrumentValue_for_SubgraphSelector" + } + }, + "required": [ + "description", + "type", + "unit", + "value" + ], + "type": "object" + }, + "Instrument_for_SupergraphAttributes_and_SupergraphSelector": { + "additionalProperties": false, + "properties": { + "attributes": { + "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SupergraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SupergraphSelector", + "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SupergraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SupergraphSelector" + }, + "condition": { + "$ref": "#/definitions/Condition_for_SupergraphSelector", + "description": "#/definitions/Condition_for_SupergraphSelector" + }, + "description": { + "description": "The description of the instrument.", + "type": "string" + }, + "type": { + "$ref": "#/definitions/InstrumentType", + "description": "#/definitions/InstrumentType" + }, + "unit": { + "description": "The units of the instrument, e.g. \"ms\", \"bytes\", \"requests\".", + "type": "string" + }, + "value": { + "$ref": "#/definitions/InstrumentValue_for_SupergraphSelector", + "description": "#/definitions/InstrumentValue_for_SupergraphSelector" + } + }, + "required": [ + "description", + "type", + "unit", + "value" + ], + "type": "object" + }, + "Instrumentation": { + "additionalProperties": false, + "description": "Instrumentation configuration", + "properties": { + "events": { + "$ref": "#/definitions/Events", + "description": "#/definitions/Events" + }, + "instruments": { + "$ref": "#/definitions/InstrumentsConfig", + "description": "#/definitions/InstrumentsConfig" + }, + "spans": { + "$ref": "#/definitions/Spans", + "description": "#/definitions/Spans" + } + }, + "type": "object" + }, + "InstrumentsConfig": { + "additionalProperties": false, + "properties": { + "default_requirement_level": { + "$ref": "#/definitions/DefaultAttributeRequirementLevel", + "description": "#/definitions/DefaultAttributeRequirementLevel" + }, + "router": { + "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::instruments::RouterInstrumentsConfig_apollo_router::plugins::telemetry::config_new::instruments::Instrument", + "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::instruments::RouterInstrumentsConfig_apollo_router::plugins::telemetry::config_new::instruments::Instrument" + }, + "subgraph": { + "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::instruments::SubgraphInstrumentsConfig_apollo_router::plugins::telemetry::config_new::instruments::Instrument", + "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::instruments::SubgraphInstrumentsConfig_apollo_router::plugins::telemetry::config_new::instruments::Instrument" + }, + "supergraph": { + "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::instruments::SupergraphInstrumentsConfig_apollo_router::plugins::telemetry::config_new::instruments::Instrument", + "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::instruments::SupergraphInstrumentsConfig_apollo_router::plugins::telemetry::config_new::instruments::Instrument" + } + }, + "type": "object" + }, + "JWTConf": { + "additionalProperties": false, + "properties": { + "header_name": { + "default": "authorization", + "description": "HTTP header expected to contain JWT", + "type": "string" + }, + "header_value_prefix": { + "default": "Bearer", + "description": "Header value prefix", + "type": "string" + }, + "ignore_other_prefixes": { + "default": false, + "description": "Whether to ignore any mismatched prefixes", + "type": "boolean" + }, + "jwks": { + "description": "List of JWKS used to verify tokens", + "items": { + "$ref": "#/definitions/JwksConf", + "description": "#/definitions/JwksConf" + }, + "type": "array" + }, + "sources": { + "description": "Alternative sources to extract the JWT", + "items": { + "$ref": "#/definitions/Source", + "description": "#/definitions/Source" + }, + "type": "array" + } + }, + "required": [ + "jwks" + ], + "type": "object" + }, + "JwksConf": { + "additionalProperties": false, + "properties": { + "algorithms": { + "description": "List of accepted algorithms. Possible values are `HS256`, `HS384`, `HS512`, `ES256`, `ES384`, `RS256`, `RS384`, `RS512`, `PS256`, `PS384`, `PS512`, `EdDSA`", + "items": { + "type": "string" + }, + "nullable": true, + "type": "array" + }, + "headers": { + "description": "List of headers to add to the JWKS request", + "items": { + "$ref": "#/definitions/Header", + "description": "#/definitions/Header" + }, + "type": "array" + }, + "issuer": { + "description": "Expected issuer for tokens verified by that JWKS", + "nullable": true, + "type": "string" + }, + "poll_interval": { + "default": { + "nanos": 0, + "secs": 60 + }, + "description": "Polling interval for each JWKS endpoint in human-readable format; defaults to 60s", + "type": "string" + }, + "url": { + "description": "Retrieve the JWK Set", + "type": "string" + } + }, + "required": [ + "url" + ], + "type": "object" + }, + "Limits": { + "additionalProperties": false, + "description": "Configuration for operation limits, parser limits, HTTP limits, etc.", + "properties": { + "http_max_request_bytes": { + "default": 2000000, + "description": "Limit the size of incoming HTTP requests read from the network, to protect against running out of memory. Default: 2000000 (2 MB)", + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "max_aliases": { + "description": "If set, requests with operations with more aliases than this maximum are rejected with a HTTP 400 Bad Request response and GraphQL error with `\"extensions\": {\"code\": \"MAX_ALIASES_LIMIT\"}`", + "format": "uint32", + "minimum": 0.0, + "nullable": true, + "type": "integer" + }, + "max_depth": { + "description": "If set, requests with operations deeper than this maximum are rejected with a HTTP 400 Bad Request response and GraphQL error with `\"extensions\": {\"code\": \"MAX_DEPTH_LIMIT\"}`\n\nCounts depth of an operation, looking at its selection sets, including fields in fragments and inline fragments. The following example has a depth of 3.\n\n```graphql query getProduct { book { # 1 ...bookDetails } }\n\nfragment bookDetails on Book { details { # 2 ... on ProductDetailsBook { country # 3 } } } ```", + "format": "uint32", + "minimum": 0.0, + "nullable": true, + "type": "integer" + }, + "max_height": { + "description": "If set, requests with operations higher than this maximum are rejected with a HTTP 400 Bad Request response and GraphQL error with `\"extensions\": {\"code\": \"MAX_DEPTH_LIMIT\"}`\n\nHeight is based on simple merging of fields using the same name or alias, but only within the same selection set. For example `name` here is only counted once and the query has height 3, not 4:\n\n```graphql query { name { first } name { last } } ```\n\nThis may change in a future version of Apollo Router to do [full field merging across fragments][merging] instead.\n\n[merging]: https://spec.graphql.org/October2021/#sec-Field-Selection-Merging]", + "format": "uint32", + "minimum": 0.0, + "nullable": true, + "type": "integer" + }, + "max_root_fields": { + "description": "If set, requests with operations with more root fields than this maximum are rejected with a HTTP 400 Bad Request response and GraphQL error with `\"extensions\": {\"code\": \"MAX_ROOT_FIELDS_LIMIT\"}`\n\nThis limit counts only the top level fields in a selection set, including fragments and inline fragments.", + "format": "uint32", + "minimum": 0.0, + "nullable": true, + "type": "integer" + }, + "parser_max_recursion": { + "default": 500, + "description": "Limit recursion in the GraphQL parser to protect against stack overflow. default: 500", + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "parser_max_tokens": { + "default": 15000, + "description": "Limit the number of tokens the GraphQL parser processes before aborting.", + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "warn_only": { + "default": false, + "description": "If set to true (which is the default is dev mode), requests that exceed a `max_*` limit are *not* rejected. Instead they are executed normally, and a warning is logged.", + "type": "boolean" + } + }, + "type": "object" + }, + "ListenAddr": { + "anyOf": [ + { + "description": "Socket address.", + "type": "string" + }, + { + "description": "Unix socket.", + "type": "string" + } + ], + "description": "Listening address." + }, + "Logging": { + "additionalProperties": false, + "description": "Logging configuration.", + "properties": { + "common": { + "$ref": "#/definitions/LoggingCommon", + "description": "#/definitions/LoggingCommon" + }, + "experimental_when_header": { + "description": "Log configuration to log request and response for subgraphs and supergraph Note that this will be removed when events are implemented.", + "items": { + "$ref": "#/definitions/HeaderLoggingCondition", + "description": "#/definitions/HeaderLoggingCondition" + }, + "type": "array" + }, + "stdout": { + "$ref": "#/definitions/StdOut", + "description": "#/definitions/StdOut" + } + }, + "type": "object" + }, + "LoggingCommon": { + "additionalProperties": false, + "properties": { + "resource": { + "additionalProperties": { + "$ref": "#/definitions/AttributeValue", + "description": "#/definitions/AttributeValue" + }, + "default": {}, + "description": "The Open Telemetry resource", + "type": "object" + }, + "service_name": { + "description": "Set a service.name resource in your metrics", + "nullable": true, + "type": "string" + }, + "service_namespace": { + "description": "Set a service.namespace attribute in your metrics", + "nullable": true, + "type": "string" + } + }, + "type": "object" + }, + "MetricAggregation": { + "oneOf": [ + { + "additionalProperties": false, + "description": "An aggregation that summarizes a set of measurements as an histogram with explicitly defined buckets.", + "properties": { + "histogram": { + "additionalProperties": false, + "properties": { + "buckets": { + "items": { + "format": "double", + "type": "number" + }, + "type": "array" + } + }, + "required": [ + "buckets" + ], + "type": "object" + } + }, + "required": [ + "histogram" + ], + "type": "object" + } + ] + }, + "MetricView": { + "additionalProperties": false, + "properties": { + "aggregation": { + "$ref": "#/definitions/MetricAggregation", + "description": "#/definitions/MetricAggregation", + "nullable": true + }, + "allowed_attribute_keys": { + "description": "An allow-list of attribute keys that will be preserved for the instrument.\n\nAny attribute recorded for the instrument with a key not in this set will be dropped. If the set is empty, all attributes will be dropped, if `None` all attributes will be kept.", + "items": { + "type": "string" + }, + "nullable": true, + "type": "array", + "uniqueItems": true + }, + "description": { + "description": "New description to set to the instrument", + "nullable": true, + "type": "string" + }, + "name": { + "description": "The instrument name you're targeting", + "type": "string" + }, + "unit": { + "description": "New unit to set to the instrument", + "nullable": true, + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "Metrics": { + "additionalProperties": false, + "description": "Per subgraph configuration for entity caching", + "properties": { + "enabled": { + "default": false, + "description": "enables metrics evaluating the benefits of entity caching", + "type": "boolean" + }, + "separate_per_type": { + "default": false, + "description": "Adds the entity type name to attributes. This can greatly increase the cardinality", + "type": "boolean" + }, + "ttl": { + "$ref": "#/definitions/Ttl", + "description": "#/definitions/Ttl", + "nullable": true + } + }, + "type": "object" + }, + "Metrics2": { + "additionalProperties": false, + "description": "Metrics configuration", + "properties": { + "common": { + "$ref": "#/definitions/MetricsCommon", + "description": "#/definitions/MetricsCommon" + }, + "otlp": { + "$ref": "#/definitions/Config9", + "description": "#/definitions/Config9" + }, + "prometheus": { + "$ref": "#/definitions/Config10", + "description": "#/definitions/Config10" + } + }, + "type": "object" + }, + "MetricsAttributesConf": { + "additionalProperties": false, + "description": "Configuration to add custom attributes/labels on metrics", + "properties": { + "subgraph": { + "$ref": "#/definitions/SubgraphAttributesConf", + "description": "#/definitions/SubgraphAttributesConf" + }, + "supergraph": { + "$ref": "#/definitions/AttributesForwardConf", + "description": "#/definitions/AttributesForwardConf" + } + }, + "type": "object" + }, + "MetricsCommon": { + "additionalProperties": false, + "properties": { + "attributes": { + "$ref": "#/definitions/MetricsAttributesConf", + "description": "#/definitions/MetricsAttributesConf" + }, + "buckets": { + "default": [ + 0.001, + 0.005, + 0.015, + 0.05, + 0.1, + 0.2, + 0.3, + 0.4, + 0.5, + 1.0, + 5.0, + 10.0 + ], + "description": "Custom buckets for all histograms", + "items": { + "format": "double", + "type": "number" + }, + "type": "array" + }, + "resource": { + "additionalProperties": { + "$ref": "#/definitions/AttributeValue", + "description": "#/definitions/AttributeValue" + }, + "default": {}, + "description": "The Open Telemetry resource", + "type": "object" + }, + "service_name": { + "description": "Set a service.name resource in your metrics", + "nullable": true, + "type": "string" + }, + "service_namespace": { + "description": "Set a service.namespace attribute in your metrics", + "nullable": true, + "type": "string" + }, + "views": { + "description": "Views applied on metrics", + "items": { + "$ref": "#/definitions/MetricView", + "description": "#/definitions/MetricView" + }, + "type": "array" + } + }, + "type": "object" + }, + "Mode": { + "enum": [ + "measure", + "enforce" + ], + "type": "string" + }, + "MultipartRequest": { + "additionalProperties": false, + "description": "Configuration for a multipart request for file uploads.\n\nThis protocol conforms to [jaydenseric's multipart spec](https://github.com/jaydenseric/graphql-multipart-request-spec)", + "properties": { + "enabled": { + "default": true, + "description": "Whether to enable the multipart protocol for file uploads (default: true)", + "type": "boolean" + }, + "limits": { + "$ref": "#/definitions/MultipartRequestLimits", + "description": "#/definitions/MultipartRequestLimits" + }, + "mode": { + "$ref": "#/definitions/MultipartRequestMode", + "description": "#/definitions/MultipartRequestMode" + } + }, + "type": "object" + }, + "MultipartRequestLimits": { + "additionalProperties": false, + "description": "Request limits for a multipart request", + "properties": { + "max_file_size": { + "description": "The maximum size of each file, in bytes (default: 5MB)", + "type": "string" + }, + "max_files": { + "description": "The maximum amount of files allowed for a single query (default: 4)", + "format": "uint", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "max_file_size", + "max_files" + ], + "type": "object" + }, + "MultipartRequestMode": { + "description": "Supported mode for a multipart request", + "oneOf": [ + { + "description": "The multipart request will not be loaded into memory and instead will be streamed directly to the subgraph in the order received. This has some limitations, mainly that the query _must_ be able to be streamed directly to the subgraph without buffering.\n\nIn practice, this means that certain queries will fail due to ordering of the files.", + "enum": [ + "stream" + ], + "type": "string" + } + ] + }, + "Operation": { + "oneOf": [ + { + "additionalProperties": false, + "properties": { + "insert": { + "$ref": "#/definitions/Insert", + "description": "#/definitions/Insert" + } + }, + "required": [ + "insert" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "remove": { + "$ref": "#/definitions/Remove", + "description": "#/definitions/Remove" + } + }, + "required": [ + "remove" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "propagate": { + "$ref": "#/definitions/Propagate", + "description": "#/definitions/Propagate" + } + }, + "required": [ + "propagate" + ], + "type": "object" + } + ] + }, + "OperationKind": { + "oneOf": [ + { + "description": "The raw operation kind.", + "enum": [ + "string" + ], + "type": "string" + } + ] + }, + "OperationName": { + "oneOf": [ + { + "description": "The raw operation name.", + "enum": [ + "string" + ], + "type": "string" + }, + { + "description": "A hash of the operation name.", + "enum": [ + "hash" + ], + "type": "string" + } + ] + }, + "PersistedQueries": { + "additionalProperties": false, + "description": "Persisted Queries (PQ) configuration", + "properties": { + "enabled": { + "default": false, + "description": "Activates Persisted Queries (disabled by default)", + "type": "boolean" + }, + "log_unknown": { + "default": false, + "description": "Enabling this field configures the router to log any freeform GraphQL request that is not in the persisted query list", + "type": "boolean" + }, + "safelist": { + "$ref": "#/definitions/PersistedQueriesSafelist", + "description": "#/definitions/PersistedQueriesSafelist" + } + }, + "type": "object" + }, + "PersistedQueriesSafelist": { + "additionalProperties": false, + "description": "Persisted Queries (PQ) Safelisting configuration", + "properties": { + "enabled": { + "default": false, + "description": "Enables using the persisted query list as a safelist (disabled by default)", + "type": "boolean" + }, + "require_id": { + "default": false, + "description": "Enabling this field configures the router to reject any request that does not include the persisted query ID", + "type": "boolean" + } + }, + "type": "object" + }, + "Plugins": { + "additionalProperties": false, + "properties": { + "experimental.broken": { + "$ref": "#/definitions/Config", + "description": "#/definitions/Config" + }, + "experimental.expose_query_plan": { + "$ref": "#/definitions/ExposeQueryPlanConfig", + "description": "#/definitions/ExposeQueryPlanConfig" + }, + "experimental.record": { + "$ref": "#/definitions/RecordConfig", + "description": "#/definitions/RecordConfig" + }, + "experimental.restricted": { + "$ref": "#/definitions/Config2", + "description": "#/definitions/Config2" + }, + "test.always_fails_to_start": { + "$ref": "#/definitions/Conf", + "description": "#/definitions/Conf" + }, + "test.always_starts_and_stops": { + "$ref": "#/definitions/Conf", + "description": "#/definitions/Conf" + } + } + }, + "Propagate": { + "anyOf": [ + { + "additionalProperties": false, + "description": "Propagate header given a header name", + "properties": { + "default": { + "description": "Default value for the header.", + "nullable": true, + "type": "string" + }, + "named": { + "description": "The source header name", + "type": "string" + }, + "rename": { + "description": "An optional target header name", + "nullable": true, + "type": "string" + } + }, + "required": [ + "named" + ], + "type": "object" + }, + { + "additionalProperties": false, + "description": "Propagate header given a regex to match header name", + "properties": { + "matching": { + "description": "The regex on header name", + "type": "string" + } + }, + "required": [ + "matching" + ], + "type": "object" + } + ], + "description": "Propagate header" + }, + "Propagation": { + "additionalProperties": false, + "description": "Configure propagation of traces. In general you won't have to do this as these are automatically configured along with any exporter you configure.", + "properties": { + "aws_xray": { + "default": false, + "description": "Propagate AWS X-Ray", + "type": "boolean" + }, + "baggage": { + "default": false, + "description": "Propagate baggage https://www.w3.org/TR/baggage/", + "type": "boolean" + }, + "datadog": { + "default": false, + "description": "Propagate Datadog", + "type": "boolean" + }, + "jaeger": { + "default": false, + "description": "Propagate Jaeger", + "type": "boolean" + }, + "request": { + "$ref": "#/definitions/RequestPropagation", + "description": "#/definitions/RequestPropagation" + }, + "trace_context": { + "default": false, + "description": "Propagate trace context https://www.w3.org/TR/trace-context/", + "type": "boolean" + }, + "zipkin": { + "default": false, + "description": "Propagate Zipkin", + "type": "boolean" + } + }, + "type": "object" + }, + "Protocol": { + "enum": [ + "grpc", + "http" + ], + "type": "string" + }, + "Query": { + "oneOf": [ + { + "description": "The raw query kind.", + "enum": [ + "string" + ], + "type": "string" + } + ] + }, + "QueryPlanCache": { + "additionalProperties": false, + "description": "Cache configuration", + "properties": { + "in_memory": { + "$ref": "#/definitions/InMemoryCache", + "description": "#/definitions/InMemoryCache" + }, + "redis": { + "$ref": "#/definitions/QueryPlanRedisCache", + "description": "#/definitions/QueryPlanRedisCache", + "nullable": true + } + }, + "type": "object" + }, + "QueryPlanRedisCache": { + "additionalProperties": false, + "description": "Redis cache configuration", + "properties": { + "namespace": { + "description": "namespace used to prefix Redis keys", + "nullable": true, + "type": "string" + }, + "password": { + "description": "Redis password if not provided in the URLs. This field takes precedence over the password in the URL", + "nullable": true, + "type": "string" + }, + "required_to_start": { + "default": false, + "description": "Prevents the router from starting if it cannot connect to Redis", + "type": "boolean" + }, + "reset_ttl": { + "default": true, + "description": "When a TTL is set on a key, reset it when reading the data from that key", + "type": "boolean" + }, + "timeout": { + "description": "Redis request timeout (default: 2ms)", + "nullable": true, + "type": "string" + }, + "tls": { + "$ref": "#/definitions/TlsClient", + "description": "#/definitions/TlsClient", + "nullable": true + }, + "ttl": { + "default": { + "nanos": 0, + "secs": 2592000 + }, + "description": "TTL for entries", + "nullable": true, + "type": "string" + }, + "urls": { + "description": "List of URLs to the Redis cluster", + "items": { + "format": "uri", + "type": "string" + }, + "type": "array" + }, + "username": { + "description": "Redis username if not provided in the URLs. This field takes precedence over the username in the URL", + "nullable": true, + "type": "string" + } + }, + "required": [ + "urls" + ], + "type": "object" + }, + "QueryPlannerMode": { + "description": "Query planner modes.", + "oneOf": [ + { + "description": "Use the new Rust-based implementation.", + "enum": [ + "new" + ], + "type": "string" + }, + { + "description": "Use the old JavaScript-based implementation.", + "enum": [ + "legacy" + ], + "type": "string" + }, + { + "description": "Use Rust-based and Javascript-based implementations side by side, logging warnings if the implementations disagree.", + "enum": [ + "both" + ], + "type": "string" + } + ] + }, + "QueryPlanning": { + "additionalProperties": false, + "description": "Query planning cache configuration", + "properties": { + "cache": { + "$ref": "#/definitions/QueryPlanCache", + "description": "#/definitions/QueryPlanCache" + }, + "experimental_parallelism": { + "$ref": "#/definitions/AvailableParallelism", + "description": "#/definitions/AvailableParallelism" + }, + "experimental_paths_limit": { + "description": "Before creating query plans, for each path of fields in the query we compute all the possible options to traverse that path via the subgraphs. Multiple options can arise because fields in the path can be provided by multiple subgraphs, and abstract types (i.e. unions and interfaces) returned by fields sometimes require the query planner to traverse through each constituent object type. The number of options generated in this computation can grow large if the schema or query are sufficiently complex, and that will increase the time spent planning.\n\nThis config allows specifying a per-path limit to the number of options considered. If any path's options exceeds this limit, query planning will abort and the operation will fail.\n\nThe default value is None, which specifies no limit.", + "format": "uint32", + "minimum": 0.0, + "nullable": true, + "type": "integer" + }, + "experimental_plans_limit": { + "description": "Sets a limit to the number of generated query plans. The planning process generates many different query plans as it explores the graph, and the list can grow large. By using this limit, we prevent that growth and still get a valid query plan, but it may not be the optimal one.\n\nThe default limit is set to 10000, but it may change in the future", + "format": "uint32", + "minimum": 0.0, + "nullable": true, + "type": "integer" + }, + "experimental_reuse_query_plans": { + "default": false, + "description": "If cache warm up is configured, this will allow the router to keep a query plan created with the old schema, if it determines that the schema update does not affect the corresponding query", + "type": "boolean" + }, + "warmed_up_queries": { + "description": "Warms up the cache on reloads by running the query plan over a list of the most used queries (from the in memory cache) Configures the number of queries warmed up. Defaults to 1/3 of the in memory cache", + "format": "uint", + "minimum": 0.0, + "nullable": true, + "type": "integer" + } + }, + "type": "object" + }, + "RateLimit": { + "additionalProperties": false, + "properties": { + "capacity": { + "default": 1, + "description": "Number of log lines allowed in interval per message", + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "enabled": { + "default": false, + "description": "Set to true to limit the rate of log messages", + "type": "boolean" + }, + "interval": { + "default": { + "nanos": 0, + "secs": 1 + }, + "description": "Interval for rate limiting", + "type": "string" + } + }, + "type": "object" + }, + "RateLimitConf": { + "additionalProperties": false, + "properties": { + "capacity": { + "description": "Number of requests allowed", + "format": "uint64", + "minimum": 1.0, + "type": "integer" + }, + "interval": { + "description": "Per interval", + "type": "string" + } + }, + "required": [ + "capacity", + "interval" + ], + "type": "object" + }, + "RecordConfig": { + "additionalProperties": false, + "description": "Request recording configuration.", + "properties": { + "enabled": { + "description": "The recording plugin is disabled by default.", + "type": "boolean" + }, + "storage_path": { + "description": "The path to the directory where recordings will be stored. Defaults to the current working directory.", + "nullable": true, + "type": "string" + } + }, + "required": [ + "enabled" + ], + "type": "object" + }, + "RedisCache": { + "additionalProperties": false, + "description": "Redis cache configuration", + "properties": { + "namespace": { + "description": "namespace used to prefix Redis keys", + "nullable": true, + "type": "string" + }, + "password": { + "description": "Redis password if not provided in the URLs. This field takes precedence over the password in the URL", + "nullable": true, + "type": "string" + }, + "required_to_start": { + "default": false, + "description": "Prevents the router from starting if it cannot connect to Redis", + "type": "boolean" + }, + "reset_ttl": { + "default": true, + "description": "When a TTL is set on a key, reset it when reading the data from that key", + "type": "boolean" + }, + "timeout": { + "description": "Redis request timeout (default: 2ms)", + "nullable": true, + "type": "string" + }, + "tls": { + "$ref": "#/definitions/TlsClient", + "description": "#/definitions/TlsClient", + "nullable": true + }, + "ttl": { + "description": "TTL for entries", + "nullable": true, + "type": "string" + }, + "urls": { + "description": "List of URLs to the Redis cluster", + "items": { + "format": "uri", + "type": "string" + }, + "type": "array" + }, + "username": { + "description": "Redis username if not provided in the URLs. This field takes precedence over the username in the URL", + "nullable": true, + "type": "string" + } + }, + "required": [ + "urls" + ], + "type": "object" + }, + "Remove": { + "description": "Remove header", + "oneOf": [ + { + "additionalProperties": false, + "description": "Remove a header given a header name", + "properties": { + "named": { + "description": "Remove a header given a header name", + "type": "string" + } + }, + "required": [ + "named" + ], + "type": "object" + }, + { + "additionalProperties": false, + "description": "Remove a header given a regex matching header name", + "properties": { + "matching": { + "description": "Remove a header given a regex matching against the header name", + "type": "string" + } + }, + "required": [ + "matching" + ], + "type": "object" + } + ] + }, + "RequestPropagation": { + "additionalProperties": false, + "properties": { + "header_name": { + "description": "Choose the header name to expose trace_id (default: apollo-trace-id)", + "type": "string" + } + }, + "required": [ + "header_name" + ], + "type": "object" + }, + "ResponseStatus": { + "oneOf": [ + { + "description": "The http status code.", + "enum": [ + "code" + ], + "type": "string" + }, + { + "description": "The http status reason.", + "enum": [ + "reason" + ], + "type": "string" + } + ] + }, + "RetryConfig": { + "additionalProperties": false, + "description": "Retry configuration", + "properties": { + "min_per_sec": { + "description": "minimum rate of retries allowed to accomodate clients that have just started issuing requests, or clients that do not issue many requests per window. The default value is 10", + "format": "uint32", + "minimum": 0.0, + "nullable": true, + "type": "integer" + }, + "retry_mutations": { + "description": "allows request retries on mutations. This should only be activated if mutations are idempotent. Disabled by default", + "nullable": true, + "type": "boolean" + }, + "retry_percent": { + "description": "percentage of calls to deposit that can be retried. This is in addition to any retries allowed for via min_per_sec. Must be between 0 and 1000, default value is 0.2", + "format": "float", + "nullable": true, + "type": "number" + }, + "ttl": { + "description": "how long a single deposit should be considered. Must be between 1 and 60 seconds, default value is 10 seconds", + "type": "string" + } + }, + "type": "object" + }, + "Router": { + "additionalProperties": false, + "description": "Router level (APQ) configuration", + "properties": { + "cache": { + "$ref": "#/definitions/Cache", + "description": "#/definitions/Cache" + } + }, + "type": "object" + }, + "RouterAttributes": { + "additionalProperties": false, + "description": "Common attributes for http server and client. See https://opentelemetry.io/docs/specs/semconv/http/http-spans/#common-attributes", + "properties": { + "baggage": { + "description": "All key values from trace baggage.", + "nullable": true, + "type": "boolean" + }, + "dd.trace_id": { + "description": "The datadog trace ID. This can be output in logs and used to correlate traces in Datadog.", + "nullable": true, + "type": "boolean" + }, + "error.type": { + "description": "Describes a class of error the operation ended with. Examples: * timeout * name_resolution_error * 500 Requirement level: Conditionally Required: If request has ended with an error.", + "nullable": true, + "type": "boolean" + }, + "http.request.body.size": { + "description": "The size of the request payload body in bytes. This is the number of bytes transferred excluding headers and is often, but not always, present as the Content-Length header. For requests using transport encoding, this should be the compressed size. Examples: * 3495 Requirement level: Recommended", + "nullable": true, + "type": "boolean" + }, + "http.request.method": { + "description": "HTTP request method. Examples: * GET * POST * HEAD Requirement level: Required", + "nullable": true, + "type": "boolean" + }, + "http.response.body.size": { + "description": "The size of the response payload body in bytes. This is the number of bytes transferred excluding headers and is often, but not always, present as the Content-Length header. For requests using transport encoding, this should be the compressed size. Examples: * 3495 Requirement level: Recommended", + "nullable": true, + "type": "boolean" + }, + "http.response.status_code": { + "description": "HTTP response status code. Examples: * 200 Requirement level: Conditionally Required: If and only if one was received/sent.", + "nullable": true, + "type": "boolean" + }, + "http.route": { + "description": "The matched route (path template in the format used by the respective server framework). Examples: * /graphql Requirement level: Conditionally Required: If and only if it’s available", + "nullable": true, + "type": "boolean" + }, + "network.local.address": { + "description": "Local socket address. Useful in case of a multi-IP host. Examples: * 10.1.2.80 * /tmp/my.sock Requirement level: Opt-In", + "nullable": true, + "type": "boolean" + }, + "network.local.port": { + "description": "Local socket port. Useful in case of a multi-port host. Examples: * 65123 Requirement level: Opt-In", + "nullable": true, + "type": "boolean" + }, + "network.peer.address": { + "description": "Peer address of the network connection - IP address or Unix domain socket name. Examples: * 10.1.2.80 * /tmp/my.sock Requirement level: Recommended", + "nullable": true, + "type": "boolean" + }, + "network.peer.port": { + "description": "Peer port number of the network connection. Examples: * 65123 Requirement level: Recommended", + "nullable": true, + "type": "boolean" + }, + "network.protocol.name": { + "description": "OSI application layer or non-OSI equivalent. Examples: * http * spdy Requirement level: Recommended: if not default (http).", + "nullable": true, + "type": "boolean" + }, + "network.protocol.version": { + "description": "Version of the protocol specified in network.protocol.name. Examples: * 1.0 * 1.1 * 2 * 3 Requirement level: Recommended", + "nullable": true, + "type": "boolean" + }, + "network.transport": { + "description": "OSI transport layer. Examples: * tcp * udp Requirement level: Conditionally Required", + "nullable": true, + "type": "boolean" + }, + "network.type": { + "description": "OSI network layer or non-OSI equivalent. Examples: * ipv4 * ipv6 Requirement level: Recommended", + "nullable": true, + "type": "boolean" + }, + "server.address": { + "description": "Name of the local HTTP server that received the request. Examples: * example.com * 10.1.2.80 * /tmp/my.sock Requirement level: Recommended", + "nullable": true, + "type": "boolean" + }, + "server.port": { + "description": "Port of the local HTTP server that received the request. Examples: * 80 * 8080 * 443 Requirement level: Recommended", + "nullable": true, + "type": "boolean" + }, + "trace_id": { + "description": "The OpenTelemetry trace ID. This can be output in logs.", + "nullable": true, + "type": "boolean" + }, + "url.path": { + "description": "The URI path component Examples: * /search Requirement level: Required", + "nullable": true, + "type": "boolean" + }, + "url.query": { + "description": "The URI query component Examples: * q=OpenTelemetry Requirement level: Conditionally Required: If and only if one was received/sent.", + "nullable": true, + "type": "boolean" + }, + "url.scheme": { + "description": "The URI scheme component identifying the used protocol. Examples: * http * https Requirement level: Required", + "nullable": true, + "type": "boolean" + }, + "user_agent.original": { + "description": "Value of the HTTP User-Agent header sent by the client. Examples: * CERN-LineMode/2.15 * libwww/2.17b3 Requirement level: Recommended", + "nullable": true, + "type": "boolean" + } + }, + "type": "object" + }, + "RouterConf": { + "additionalProperties": false, + "properties": { + "jwt": { + "$ref": "#/definitions/JWTConf", + "description": "#/definitions/JWTConf" + } + }, + "required": [ + "jwt" + ], + "type": "object" + }, + "RouterEventsConfig": { + "additionalProperties": false, + "properties": { + "error": { + "$ref": "#/definitions/EventLevel", + "description": "#/definitions/EventLevel" + }, + "request": { + "$ref": "#/definitions/EventLevel", + "description": "#/definitions/EventLevel" + }, + "response": { + "$ref": "#/definitions/EventLevel", + "description": "#/definitions/EventLevel" + } + }, + "type": "object" + }, + "RouterInstrumentsConfig": { + "additionalProperties": false, + "properties": { + "http.server.active_requests": { + "$ref": "#/definitions/DefaultedStandardInstrument_for_ActiveRequestsAttributes", + "description": "#/definitions/DefaultedStandardInstrument_for_ActiveRequestsAttributes" + }, + "http.server.request.body.size": { + "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::RouterAttributes_apollo_router::plugins::telemetry::config_new::selectors::RouterSelector", + "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::RouterAttributes_apollo_router::plugins::telemetry::config_new::selectors::RouterSelector" + }, + "http.server.request.duration": { + "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::RouterAttributes_apollo_router::plugins::telemetry::config_new::selectors::RouterSelector", + "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::RouterAttributes_apollo_router::plugins::telemetry::config_new::selectors::RouterSelector" + }, + "http.server.response.body.size": { + "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::RouterAttributes_apollo_router::plugins::telemetry::config_new::selectors::RouterSelector", + "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::RouterAttributes_apollo_router::plugins::telemetry::config_new::selectors::RouterSelector" + } + }, + "type": "object" + }, + "RouterRequestConf": { + "additionalProperties": false, + "description": "What information is passed to a router request/response stage", + "properties": { + "body": { + "default": false, + "description": "Send the body", + "type": "boolean" + }, + "context": { + "default": false, + "description": "Send the context", + "type": "boolean" + }, + "headers": { + "default": false, + "description": "Send the headers", + "type": "boolean" + }, + "method": { + "default": false, + "description": "Send the method", + "type": "boolean" + }, + "path": { + "default": false, + "description": "Send the path", + "type": "boolean" + }, + "sdl": { + "default": false, + "description": "Send the SDL", + "type": "boolean" + } + }, + "type": "object" + }, + "RouterResponseConf": { + "additionalProperties": false, + "description": "What information is passed to a router request/response stage", + "properties": { + "body": { + "default": false, + "description": "Send the body", + "type": "boolean" + }, + "context": { + "default": false, + "description": "Send the context", + "type": "boolean" + }, + "headers": { + "default": false, + "description": "Send the headers", + "type": "boolean" + }, + "sdl": { + "default": false, + "description": "Send the SDL", + "type": "boolean" + }, + "status_code": { + "default": false, + "description": "Send the HTTP status", + "type": "boolean" + } + }, + "type": "object" + }, + "RouterSelector": { + "anyOf": [ + { + "additionalProperties": false, + "description": "A header from the request", + "properties": { + "default": { + "$ref": "#/definitions/AttributeValue", + "description": "#/definitions/AttributeValue", + "nullable": true + }, + "request_header": { + "description": "The name of the request header.", + "type": "string" + } + }, + "required": [ + "request_header" + ], + "type": "object" + }, + { + "additionalProperties": false, + "description": "The request method.", + "properties": { + "request_method": { + "description": "The request method enabled or not", + "type": "boolean" + } + }, + "required": [ + "request_method" + ], + "type": "object" + }, + { + "additionalProperties": false, + "description": "A header from the response", + "properties": { + "default": { + "$ref": "#/definitions/AttributeValue", + "description": "#/definitions/AttributeValue", + "nullable": true + }, + "response_header": { + "description": "The name of the request header.", + "type": "string" + } + }, + "required": [ + "response_header" + ], + "type": "object" + }, + { + "additionalProperties": false, + "description": "A status from the response", + "properties": { + "response_status": { + "$ref": "#/definitions/ResponseStatus", + "description": "#/definitions/ResponseStatus" + } + }, + "required": [ + "response_status" + ], + "type": "object" + }, + { + "additionalProperties": false, + "description": "The trace ID of the request.", + "properties": { + "trace_id": { + "$ref": "#/definitions/TraceIdFormat2", + "description": "#/definitions/TraceIdFormat2" + } + }, + "required": [ + "trace_id" + ], + "type": "object" + }, + { + "additionalProperties": false, + "description": "A value from context.", + "properties": { + "default": { + "$ref": "#/definitions/AttributeValue", + "description": "#/definitions/AttributeValue", + "nullable": true + }, + "response_context": { + "description": "The response context key.", + "type": "string" + } + }, + "required": [ + "response_context" + ], + "type": "object" + }, + { + "additionalProperties": false, + "description": "A value from baggage.", + "properties": { + "baggage": { + "description": "The name of the baggage item.", + "type": "string" + }, + "default": { + "$ref": "#/definitions/AttributeValue", + "description": "#/definitions/AttributeValue", + "nullable": true + } + }, + "required": [ + "baggage" + ], + "type": "object" + }, + { + "additionalProperties": false, + "description": "A value from an environment variable.", + "properties": { + "default": { + "description": "Optional default value.", + "nullable": true, + "type": "string" + }, + "env": { + "description": "The name of the environment variable", + "type": "string" + } + }, + "required": [ + "env" + ], + "type": "object" + }, + { + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "static": { + "description": "A static string value", + "type": "string" + } + }, + "required": [ + "static" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "on_graphql_error": { + "description": "Boolean set to true if the response body contains graphql error", + "type": "boolean" + } + }, + "required": [ + "on_graphql_error" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "error": { + "$ref": "#/definitions/ErrorRepr", + "description": "#/definitions/ErrorRepr" + } + }, + "required": [ + "error" + ], + "type": "object" + } + ] + }, + "RouterShaping": { + "additionalProperties": false, + "properties": { + "global_rate_limit": { + "$ref": "#/definitions/RateLimitConf", + "description": "#/definitions/RateLimitConf", + "nullable": true + }, + "timeout": { + "description": "Enable timeout for incoming requests", + "type": "string" + } + }, + "type": "object" + }, + "RouterSpans": { + "additionalProperties": false, + "properties": { + "attributes": { + "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::RouterAttributes_apollo_router::plugins::telemetry::config_new::conditional::Conditional", + "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::RouterAttributes_apollo_router::plugins::telemetry::config_new::conditional::Conditional" + } + }, + "type": "object" + }, + "RouterStage": { + "properties": { + "request": { + "$ref": "#/definitions/RouterRequestConf", + "description": "#/definitions/RouterRequestConf" + }, + "response": { + "$ref": "#/definitions/RouterResponseConf", + "description": "#/definitions/RouterResponseConf" + } + }, + "type": "object" + }, + "Sampler": { + "oneOf": [ + { + "description": "Always sample", + "enum": [ + "always_on" + ], + "type": "string" + }, + { + "description": "Never sample", + "enum": [ + "always_off" + ], + "type": "string" + } + ] + }, + "SamplerOption": { + "anyOf": [ + { + "description": "Sample a given fraction. Fractions >= 1 will always sample.", + "format": "double", + "type": "number" + }, + { + "$ref": "#/definitions/Sampler", + "description": "#/definitions/Sampler" + } + ] + }, + "Sandbox": { + "additionalProperties": false, + "description": "Configuration options pertaining to the sandbox page.", + "properties": { + "enabled": { + "default": false, + "description": "Set to true to enable sandbox", + "type": "boolean" + } + }, + "type": "object" + }, + "SelectorOrValue_for_RouterSelector": { + "anyOf": [ + { + "$ref": "#/definitions/AttributeValue", + "description": "#/definitions/AttributeValue" + }, + { + "$ref": "#/definitions/RouterSelector", + "description": "#/definitions/RouterSelector" + } + ] + }, + "SelectorOrValue_for_SubgraphSelector": { + "anyOf": [ + { + "$ref": "#/definitions/AttributeValue", + "description": "#/definitions/AttributeValue" + }, + { + "$ref": "#/definitions/SubgraphSelector", + "description": "#/definitions/SubgraphSelector" + } + ] + }, + "SelectorOrValue_for_SupergraphSelector": { + "anyOf": [ + { + "$ref": "#/definitions/AttributeValue", + "description": "#/definitions/AttributeValue" + }, + { + "$ref": "#/definitions/SupergraphSelector", + "description": "#/definitions/SupergraphSelector" + } + ] + }, + "SocketEndpoint": { + "type": "string" + }, + "Source": { + "oneOf": [ + { + "additionalProperties": false, + "properties": { + "name": { + "default": "authorization", + "description": "HTTP header expected to contain JWT", + "type": "string" + }, + "type": { + "enum": [ + "header" + ], + "type": "string" + }, + "value_prefix": { + "default": "Bearer", + "description": "Header value prefix", + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "name": { + "description": "Name of the cookie containing the JWT", + "type": "string" + }, + "type": { + "enum": [ + "cookie" + ], + "type": "string" + } + }, + "required": [ + "name", + "type" + ], + "type": "object" + } + ] + }, + "SpanMode": { + "description": "Span mode to create new or deprecated spans", + "oneOf": [ + { + "description": "Keep the request span as root span and deprecated attributes. This option will eventually removed.", + "enum": [ + "deprecated" + ], + "type": "string" + }, + { + "description": "Use new OpenTelemetry spec compliant span attributes or preserve existing. This will be the default in future.", + "enum": [ + "spec_compliant" + ], + "type": "string" + } + ] + }, + "Spans": { + "additionalProperties": false, + "properties": { + "default_attribute_requirement_level": { + "$ref": "#/definitions/DefaultAttributeRequirementLevel", + "description": "#/definitions/DefaultAttributeRequirementLevel" + }, + "mode": { + "$ref": "#/definitions/SpanMode", + "description": "#/definitions/SpanMode" + }, + "router": { + "$ref": "#/definitions/RouterSpans", + "description": "#/definitions/RouterSpans" + }, + "subgraph": { + "$ref": "#/definitions/SubgraphSpans", + "description": "#/definitions/SubgraphSpans" + }, + "supergraph": { + "$ref": "#/definitions/SupergraphSpans", + "description": "#/definitions/SupergraphSpans" + } + }, + "type": "object" + }, + "Standard": { + "enum": [ + "duration", + "unit" + ], + "type": "string" + }, + "StdOut": { + "additionalProperties": false, + "properties": { + "enabled": { + "default": true, + "description": "Set to true to log to stdout.", + "type": "boolean" + }, + "format": { + "$ref": "#/definitions/logging_format", + "description": "#/definitions/logging_format" + }, + "rate_limit": { + "$ref": "#/definitions/RateLimit", + "description": "#/definitions/RateLimit" + }, + "tty_format": { + "$ref": "#/definitions/logging_format", + "description": "#/definitions/logging_format", + "nullable": true + } + }, + "type": "object" + }, + "StrategyConfig": { + "description": "Algorithm for calculating the cost of an incoming query.", + "oneOf": [ + { + "additionalProperties": false, + "description": "A simple, statically-defined cost mapping for operations and types.\n\nOperation costs: - Mutation: 10 - Query: 0 - Subscription 0\n\nType costs: - Object: 1 - Interface: 1 - Union: 1 - Scalar: 0 - Enum: 0", + "properties": { + "static_estimated": { + "additionalProperties": false, + "properties": { + "list_size": { + "description": "The assumed length of lists returned by the operation.", + "format": "uint32", + "minimum": 0.0, + "type": "integer" }, - { - "type": "object", - "required": [ - "subgraph_response_status" - ], - "properties": { - "subgraph_response_status": { - "description": "The subgraph http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false + "max": { + "description": "The maximum cost of a query", + "format": "double", + "type": "number" + } + }, + "required": [ + "list_size", + "max" + ], + "type": "object" + } + }, + "required": [ + "static_estimated" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "test": { + "additionalProperties": false, + "properties": { + "error": { + "$ref": "#/definitions/TestError", + "description": "#/definitions/TestError" + }, + "stage": { + "$ref": "#/definitions/TestStage", + "description": "#/definitions/TestStage" + } + }, + "required": [ + "error", + "stage" + ], + "type": "object" + } + }, + "required": [ + "test" + ], + "type": "object" + } + ] + }, + "Subgraph": { + "additionalProperties": false, + "description": "Per subgraph configuration for entity caching", + "properties": { + "enabled": { + "description": "activates caching for this subgraph, overrides the global configuration", + "nullable": true, + "type": "boolean" + }, + "private_id": { + "description": "Context key used to separate cache sections per user", + "nullable": true, + "type": "string" + }, + "ttl": { + "$ref": "#/definitions/Ttl", + "description": "#/definitions/Ttl", + "nullable": true + } + }, + "type": "object" + }, + "SubgraphApq": { + "additionalProperties": false, + "description": "Subgraph level Automatic Persisted Queries (APQ) configuration", + "properties": { + "enabled": { + "default": false, + "description": "Enable", + "type": "boolean" + } + }, + "type": "object" + }, + "SubgraphAttributes": { + "additionalProperties": false, + "properties": { + "subgraph.graphql.document": { + "description": "The GraphQL document being executed. Examples: * query findBookById { bookById(id: ?) { name } } Requirement level: Recommended", + "nullable": true, + "type": "boolean" + }, + "subgraph.graphql.operation.name": { + "description": "The name of the operation being executed. Examples: * findBookById Requirement level: Recommended", + "nullable": true, + "type": "boolean" + }, + "subgraph.graphql.operation.type": { + "description": "The type of the operation being executed. Examples: * query * subscription * mutation Requirement level: Recommended", + "nullable": true, + "type": "boolean" + }, + "subgraph.name": { + "description": "The name of the subgraph Examples: * products Requirement level: Required", + "nullable": true, + "type": "boolean" + } + }, + "type": "object" + }, + "SubgraphAttributesConf": { + "additionalProperties": false, + "description": "Configuration to add custom attributes/labels on metrics to subgraphs", + "properties": { + "all": { + "$ref": "#/definitions/AttributesForwardConf", + "description": "#/definitions/AttributesForwardConf" + }, + "subgraphs": { + "additionalProperties": { + "$ref": "#/definitions/AttributesForwardConf", + "description": "#/definitions/AttributesForwardConf" + }, + "description": "Attributes per subgraph", + "type": "object" + } + }, + "type": "object" + }, + "SubgraphConfiguration_for_CommonBatchingConfig": { + "description": "Configuration options pertaining to the subgraph server component.", + "properties": { + "all": { + "$ref": "#/definitions/CommonBatchingConfig", + "description": "#/definitions/CommonBatchingConfig" + }, + "subgraphs": { + "additionalProperties": { + "$ref": "#/definitions/CommonBatchingConfig", + "description": "#/definitions/CommonBatchingConfig" + }, + "default": {}, + "description": "per subgraph options", + "type": "object" + } + }, + "type": "object" + }, + "SubgraphConfiguration_for_SubgraphApq": { + "description": "Configuration options pertaining to the subgraph server component.", + "properties": { + "all": { + "$ref": "#/definitions/SubgraphApq", + "description": "#/definitions/SubgraphApq" + }, + "subgraphs": { + "additionalProperties": { + "$ref": "#/definitions/SubgraphApq", + "description": "#/definitions/SubgraphApq" + }, + "default": {}, + "description": "per subgraph options", + "type": "object" + } + }, + "type": "object" + }, + "SubgraphConfiguration_for_TlsClient": { + "description": "Configuration options pertaining to the subgraph server component.", + "properties": { + "all": { + "$ref": "#/definitions/TlsClient", + "description": "#/definitions/TlsClient" + }, + "subgraphs": { + "additionalProperties": { + "$ref": "#/definitions/TlsClient", + "description": "#/definitions/TlsClient" + }, + "default": {}, + "description": "per subgraph options", + "type": "object" + } + }, + "type": "object" + }, + "SubgraphErrorConfig": { + "additionalProperties": false, + "properties": { + "all": { + "$ref": "#/definitions/ErrorConfiguration", + "description": "#/definitions/ErrorConfiguration" + }, + "subgraphs": { + "additionalProperties": { + "$ref": "#/definitions/ErrorConfiguration", + "description": "#/definitions/ErrorConfiguration" + }, + "description": "Handling of errors coming from specified subgraphs", + "type": "object" + } + }, + "type": "object" + }, + "SubgraphEventsConfig": { + "additionalProperties": false, + "properties": { + "error": { + "$ref": "#/definitions/EventLevel", + "description": "#/definitions/EventLevel" + }, + "request": { + "$ref": "#/definitions/EventLevel", + "description": "#/definitions/EventLevel" + }, + "response": { + "$ref": "#/definitions/EventLevel", + "description": "#/definitions/EventLevel" + } + }, + "type": "object" + }, + "SubgraphInstrumentsConfig": { + "additionalProperties": false, + "properties": { + "http.client.request.body.size": { + "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SubgraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SubgraphSelector", + "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SubgraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SubgraphSelector" + }, + "http.client.request.duration": { + "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SubgraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SubgraphSelector", + "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SubgraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SubgraphSelector" + }, + "http.client.response.body.size": { + "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SubgraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SubgraphSelector", + "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SubgraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SubgraphSelector" + } + }, + "type": "object" + }, + "SubgraphPassthroughMode": { + "additionalProperties": false, + "properties": { + "all": { + "$ref": "#/definitions/WebSocketConfiguration", + "description": "#/definitions/WebSocketConfiguration", + "nullable": true + }, + "subgraphs": { + "additionalProperties": { + "$ref": "#/definitions/WebSocketConfiguration", + "description": "#/definitions/WebSocketConfiguration" + }, + "default": {}, + "description": "Configuration for specific subgraphs", + "type": "object" + } + }, + "type": "object" + }, + "SubgraphRequestConf": { + "additionalProperties": false, + "description": "What information is passed to a subgraph request/response stage", + "properties": { + "body": { + "default": false, + "description": "Send the body", + "type": "boolean" + }, + "context": { + "default": false, + "description": "Send the context", + "type": "boolean" + }, + "headers": { + "default": false, + "description": "Send the headers", + "type": "boolean" + }, + "method": { + "default": false, + "description": "Send the method URI", + "type": "boolean" + }, + "service_name": { + "default": false, + "description": "Send the service name", + "type": "boolean" + }, + "uri": { + "default": false, + "description": "Send the subgraph URI", + "type": "boolean" + } + }, + "type": "object" + }, + "SubgraphResponseConf": { + "additionalProperties": false, + "description": "What information is passed to a subgraph request/response stage", + "properties": { + "body": { + "default": false, + "description": "Send the body", + "type": "boolean" + }, + "context": { + "default": false, + "description": "Send the context", + "type": "boolean" + }, + "headers": { + "default": false, + "description": "Send the headers", + "type": "boolean" + }, + "service_name": { + "default": false, + "description": "Send the service name", + "type": "boolean" + }, + "status_code": { + "default": false, + "description": "Send the http status", + "type": "boolean" + } + }, + "type": "object" + }, + "SubgraphSelector": { + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "default": { + "description": "Optional default value.", + "nullable": true, + "type": "string" + }, + "subgraph_operation_name": { + "$ref": "#/definitions/OperationName", + "description": "#/definitions/OperationName" + } + }, + "required": [ + "subgraph_operation_name" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "subgraph_operation_kind": { + "$ref": "#/definitions/OperationKind", + "description": "#/definitions/OperationKind" + } + }, + "required": [ + "subgraph_operation_kind" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "default": { + "description": "Optional default value.", + "nullable": true, + "type": "string" + }, + "subgraph_query": { + "$ref": "#/definitions/Query", + "description": "#/definitions/Query" + } + }, + "required": [ + "subgraph_query" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "default": { + "$ref": "#/definitions/AttributeValue", + "description": "#/definitions/AttributeValue", + "nullable": true + }, + "subgraph_query_variable": { + "description": "The name of a subgraph query variable.", + "type": "string" + } + }, + "required": [ + "subgraph_query_variable" + ], + "type": "object" + }, + { + "additionalProperties": false, + "description": "Deprecated, use SubgraphResponseData and SubgraphResponseError instead", + "properties": { + "default": { + "$ref": "#/definitions/AttributeValue", + "description": "#/definitions/AttributeValue", + "nullable": true + }, + "subgraph_response_body": { + "description": "The subgraph response body json path.", + "type": "string" + } + }, + "required": [ + "subgraph_response_body" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "default": { + "$ref": "#/definitions/AttributeValue", + "description": "#/definitions/AttributeValue", + "nullable": true + }, + "subgraph_response_data": { + "description": "The subgraph response body json path.", + "type": "string" + } + }, + "required": [ + "subgraph_response_data" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "default": { + "$ref": "#/definitions/AttributeValue", + "description": "#/definitions/AttributeValue", + "nullable": true + }, + "subgraph_response_errors": { + "description": "The subgraph response body json path.", + "type": "string" + } + }, + "required": [ + "subgraph_response_errors" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "default": { + "description": "Optional default value.", + "nullable": true, + "type": "string" + }, + "subgraph_request_header": { + "description": "The name of a subgraph request header.", + "type": "string" + } + }, + "required": [ + "subgraph_request_header" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "default": { + "description": "Optional default value.", + "nullable": true, + "type": "string" + }, + "subgraph_response_header": { + "description": "The name of a subgraph response header.", + "type": "string" + } + }, + "required": [ + "subgraph_response_header" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "subgraph_response_status": { + "$ref": "#/definitions/ResponseStatus", + "description": "#/definitions/ResponseStatus" + } + }, + "required": [ + "subgraph_response_status" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "default": { + "description": "Optional default value.", + "nullable": true, + "type": "string" + }, + "supergraph_operation_name": { + "$ref": "#/definitions/OperationName", + "description": "#/definitions/OperationName" + } + }, + "required": [ + "supergraph_operation_name" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "supergraph_operation_kind": { + "$ref": "#/definitions/OperationKind", + "description": "#/definitions/OperationKind" + } + }, + "required": [ + "supergraph_operation_kind" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "default": { + "description": "Optional default value.", + "nullable": true, + "type": "string" + }, + "supergraph_query": { + "$ref": "#/definitions/Query", + "description": "#/definitions/Query" + } + }, + "required": [ + "supergraph_query" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "default": { + "$ref": "#/definitions/AttributeValue", + "description": "#/definitions/AttributeValue", + "nullable": true + }, + "supergraph_query_variable": { + "description": "The supergraph query variable name.", + "type": "string" + } + }, + "required": [ + "supergraph_query_variable" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "default": { + "description": "Optional default value.", + "nullable": true, + "type": "string" + }, + "supergraph_request_header": { + "description": "The supergraph request header name.", + "type": "string" + } + }, + "required": [ + "supergraph_request_header" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "default": { + "$ref": "#/definitions/AttributeValue", + "description": "#/definitions/AttributeValue", + "nullable": true + }, + "request_context": { + "description": "The request context key.", + "type": "string" + } + }, + "required": [ + "request_context" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "default": { + "$ref": "#/definitions/AttributeValue", + "description": "#/definitions/AttributeValue", + "nullable": true + }, + "response_context": { + "description": "The response context key.", + "type": "string" + } + }, + "required": [ + "response_context" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "baggage": { + "description": "The name of the baggage item.", + "type": "string" + }, + "default": { + "$ref": "#/definitions/AttributeValue", + "description": "#/definitions/AttributeValue", + "nullable": true + } + }, + "required": [ + "baggage" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "default": { + "description": "Optional default value.", + "nullable": true, + "type": "string" + }, + "env": { + "description": "The name of the environment variable", + "type": "string" + } + }, + "required": [ + "env" + ], + "type": "object" + }, + { + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "static": { + "description": "A static string value", + "type": "string" + } + }, + "required": [ + "static" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "error": { + "$ref": "#/definitions/ErrorRepr", + "description": "#/definitions/ErrorRepr" + } + }, + "required": [ + "error" + ], + "type": "object" + } + ] + }, + "SubgraphShaping": { + "additionalProperties": false, + "description": "Traffic shaping options", + "properties": { + "compression": { + "$ref": "#/definitions/Compression", + "description": "#/definitions/Compression", + "nullable": true + }, + "deduplicate_query": { + "description": "Enable query deduplication", + "nullable": true, + "type": "boolean" + }, + "experimental_http2": { + "$ref": "#/definitions/Http2Config", + "description": "#/definitions/Http2Config", + "nullable": true + }, + "experimental_retry": { + "$ref": "#/definitions/RetryConfig", + "description": "#/definitions/RetryConfig", + "nullable": true + }, + "global_rate_limit": { + "$ref": "#/definitions/RateLimitConf", + "description": "#/definitions/RateLimitConf", + "nullable": true + }, + "timeout": { + "description": "Enable timeout for incoming requests", + "type": "string" + } + }, + "type": "object" + }, + "SubgraphSpans": { + "additionalProperties": false, + "properties": { + "attributes": { + "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SubgraphAttributes_apollo_router::plugins::telemetry::config_new::conditional::Conditional", + "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SubgraphAttributes_apollo_router::plugins::telemetry::config_new::conditional::Conditional" + } + }, + "type": "object" + }, + "SubgraphStage": { + "additionalProperties": false, + "description": "What information is passed to a subgraph request/response stage", + "properties": { + "request": { + "$ref": "#/definitions/SubgraphRequestConf", + "description": "#/definitions/SubgraphRequestConf" + }, + "response": { + "$ref": "#/definitions/SubgraphResponseConf", + "description": "#/definitions/SubgraphResponseConf" + } + }, + "type": "object" + }, + "SubgraphStages": { + "additionalProperties": false, + "description": "What information is passed to a subgraph request/response stage", + "properties": { + "all": { + "$ref": "#/definitions/SubgraphStage", + "description": "#/definitions/SubgraphStage" + } + }, + "type": "object" + }, + "SubscriptionConfig": { + "additionalProperties": false, + "description": "Subscriptions configuration", + "properties": { + "enable_deduplication": { + "default": true, + "description": "Enable the deduplication of subscription (for example if we detect the exact same request to subgraph we won't open a new websocket to the subgraph in passthrough mode) (default: true)", + "type": "boolean" + }, + "enabled": { + "default": true, + "description": "Enable subscription", + "type": "boolean" + }, + "max_opened_subscriptions": { + "description": "This is a limit to only have maximum X opened subscriptions at the same time. By default if it's not set there is no limit.", + "format": "uint", + "minimum": 0.0, + "nullable": true, + "type": "integer" + }, + "mode": { + "$ref": "#/definitions/SubscriptionModeConfig", + "description": "#/definitions/SubscriptionModeConfig" + }, + "queue_capacity": { + "description": "It represent the capacity of the in memory queue to know how many events we can keep in a buffer", + "format": "uint", + "minimum": 0.0, + "nullable": true, + "type": "integer" + } + }, + "type": "object" + }, + "SubscriptionModeConfig": { + "additionalProperties": false, + "properties": { + "callback": { + "$ref": "#/definitions/CallbackMode", + "description": "#/definitions/CallbackMode", + "nullable": true + }, + "passthrough": { + "$ref": "#/definitions/SubgraphPassthroughMode", + "description": "#/definitions/SubgraphPassthroughMode", + "nullable": true + } + }, + "type": "object" + }, + "Supergraph": { + "additionalProperties": false, + "description": "Configuration options pertaining to the supergraph server component.", + "properties": { + "defer_support": { + "default": true, + "description": "Set to false to disable defer support", + "type": "boolean" + }, + "early_cancel": { + "default": false, + "description": "abort request handling when the client drops the connection. Default: false. When set to true, some parts of the request pipeline like telemetry will not work properly, but request handling will stop immediately when the client connection is closed.", + "type": "boolean" + }, + "experimental_log_on_broken_pipe": { + "default": false, + "description": "Log a message if the client closes the connection before the response is sent. Default: false.", + "type": "boolean" + }, + "experimental_reuse_query_fragments": { + "description": "Enable reuse of query fragments Default: depends on the federation version", + "nullable": true, + "type": "boolean" + }, + "generate_query_fragments": { + "default": false, + "description": "Enable QP generation of fragments for subgraph requests Default: false", + "type": "boolean" + }, + "introspection": { + "default": false, + "description": "Enable introspection Default: false", + "type": "boolean" + }, + "listen": { + "$ref": "#/definitions/ListenAddr", + "description": "#/definitions/ListenAddr" + }, + "path": { + "default": "/", + "description": "The HTTP path on which GraphQL requests will be served. default: \"/\"", + "type": "string" + }, + "query_planning": { + "$ref": "#/definitions/QueryPlanning", + "description": "#/definitions/QueryPlanning" + } + }, + "type": "object" + }, + "SupergraphAttributes": { + "additionalProperties": false, + "description": "Attributes for Cost", + "properties": { + "cost.actual": { + "description": "The actual cost of the operation using the currently configured cost model", + "nullable": true, + "type": "boolean" + }, + "cost.delta": { + "description": "The delta (estimated - actual) cost of the operation using the currently configured cost model", + "nullable": true, + "type": "boolean" + }, + "cost.estimated": { + "description": "The estimated cost of the operation using the currently configured cost model", + "nullable": true, + "type": "boolean" + }, + "cost.result": { + "description": "The cost result, this is an error code returned by the cost calculation or COST_OK", + "nullable": true, + "type": "boolean" + }, + "graphql.document": { + "description": "The GraphQL document being executed. Examples: * query findBookById { bookById(id: ?) { name } } Requirement level: Recommended", + "nullable": true, + "type": "boolean" + }, + "graphql.operation.name": { + "description": "The name of the operation being executed. Examples: * findBookById Requirement level: Recommended", + "nullable": true, + "type": "boolean" + }, + "graphql.operation.type": { + "description": "The type of the operation being executed. Examples: * query * subscription * mutation Requirement level: Recommended", + "nullable": true, + "type": "boolean" + } + }, + "type": "object" + }, + "SupergraphEventsConfig": { + "additionalProperties": false, + "properties": { + "error": { + "$ref": "#/definitions/EventLevel", + "description": "#/definitions/EventLevel" + }, + "request": { + "$ref": "#/definitions/EventLevel", + "description": "#/definitions/EventLevel" + }, + "response": { + "$ref": "#/definitions/EventLevel", + "description": "#/definitions/EventLevel" + } + }, + "type": "object" + }, + "SupergraphInstrumentsConfig": { + "additionalProperties": false, + "properties": { + "cost.actual": { + "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SupergraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SupergraphSelector", + "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SupergraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SupergraphSelector" + }, + "cost.delta": { + "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SupergraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SupergraphSelector", + "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SupergraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SupergraphSelector" + }, + "cost.estimated": { + "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SupergraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SupergraphSelector", + "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SupergraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SupergraphSelector" + } + }, + "type": "object" + }, + "SupergraphRequestConf": { + "additionalProperties": false, + "description": "What information is passed to a router request/response stage", + "properties": { + "body": { + "default": false, + "description": "Send the body", + "type": "boolean" + }, + "context": { + "default": false, + "description": "Send the context", + "type": "boolean" + }, + "headers": { + "default": false, + "description": "Send the headers", + "type": "boolean" + }, + "method": { + "default": false, + "description": "Send the method", + "type": "boolean" + }, + "sdl": { + "default": false, + "description": "Send the SDL", + "type": "boolean" + } + }, + "type": "object" + }, + "SupergraphResponseConf": { + "additionalProperties": false, + "description": "What information is passed to a router request/response stage", + "properties": { + "body": { + "default": false, + "description": "Send the body", + "type": "boolean" + }, + "context": { + "default": false, + "description": "Send the context", + "type": "boolean" + }, + "headers": { + "default": false, + "description": "Send the headers", + "type": "boolean" + }, + "sdl": { + "default": false, + "description": "Send the SDL", + "type": "boolean" + }, + "status_code": { + "default": false, + "description": "Send the HTTP status", + "type": "boolean" + } + }, + "type": "object" + }, + "SupergraphSelector": { + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "default": { + "description": "Optional default value.", + "nullable": true, + "type": "string" + }, + "operation_name": { + "$ref": "#/definitions/OperationName", + "description": "#/definitions/OperationName" + } + }, + "required": [ + "operation_name" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "operation_kind": { + "$ref": "#/definitions/OperationKind", + "description": "#/definitions/OperationKind" + } + }, + "required": [ + "operation_kind" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "default": { + "description": "Optional default value.", + "nullable": true, + "type": "string" + }, + "query": { + "$ref": "#/definitions/Query", + "description": "#/definitions/Query" + } + }, + "required": [ + "query" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "default": { + "$ref": "#/definitions/AttributeValue", + "description": "#/definitions/AttributeValue", + "nullable": true + }, + "query_variable": { + "description": "The name of a graphql query variable.", + "type": "string" + } + }, + "required": [ + "query_variable" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "default": { + "description": "Optional default value.", + "nullable": true, + "type": "string" + }, + "request_header": { + "description": "The name of the request header.", + "type": "string" + } + }, + "required": [ + "request_header" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "default": { + "description": "Optional default value.", + "nullable": true, + "type": "string" + }, + "response_header": { + "description": "The name of the response header.", + "type": "string" + } + }, + "required": [ + "response_header" + ], + "type": "object" + }, + { + "additionalProperties": false, + "description": "A status from the response", + "properties": { + "response_status": { + "$ref": "#/definitions/ResponseStatus", + "description": "#/definitions/ResponseStatus" + } + }, + "required": [ + "response_status" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "default": { + "$ref": "#/definitions/AttributeValue", + "description": "#/definitions/AttributeValue", + "nullable": true + }, + "request_context": { + "description": "The request context key.", + "type": "string" + } + }, + "required": [ + "request_context" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "default": { + "$ref": "#/definitions/AttributeValue", + "description": "#/definitions/AttributeValue", + "nullable": true + }, + "response_context": { + "description": "The response context key.", + "type": "string" + } + }, + "required": [ + "response_context" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "default": { + "$ref": "#/definitions/AttributeValue", + "description": "#/definitions/AttributeValue", + "nullable": true + }, + "response_data": { + "description": "The supergraph response body json path of the chunks.", + "type": "string" + } + }, + "required": [ + "response_data" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "default": { + "$ref": "#/definitions/AttributeValue", + "description": "#/definitions/AttributeValue", + "nullable": true + }, + "response_errors": { + "description": "The supergraph response body json path of the chunks.", + "type": "string" + } + }, + "required": [ + "response_errors" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "baggage": { + "description": "The name of the baggage item.", + "type": "string" + }, + "default": { + "$ref": "#/definitions/AttributeValue", + "description": "#/definitions/AttributeValue", + "nullable": true + } + }, + "required": [ + "baggage" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "default": { + "description": "Optional default value.", + "nullable": true, + "type": "string" + }, + "env": { + "description": "The name of the environment variable", + "type": "string" + } + }, + "required": [ + "env" + ], + "type": "object" + }, + { + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "static": { + "description": "A static string value", + "type": "string" + } + }, + "required": [ + "static" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "error": { + "$ref": "#/definitions/ErrorRepr", + "description": "#/definitions/ErrorRepr" + } + }, + "required": [ + "error" + ], + "type": "object" + }, + { + "additionalProperties": false, + "description": "Cost attributes", + "properties": { + "cost": { + "$ref": "#/definitions/CostValue", + "description": "#/definitions/CostValue" + } + }, + "required": [ + "cost" + ], + "type": "object" + } + ] + }, + "SupergraphSpans": { + "additionalProperties": false, + "properties": { + "attributes": { + "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SupergraphAttributes_apollo_router::plugins::telemetry::config_new::conditional::Conditional", + "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SupergraphAttributes_apollo_router::plugins::telemetry::config_new::conditional::Conditional" + } + }, + "type": "object" + }, + "SupergraphStage": { + "properties": { + "request": { + "$ref": "#/definitions/SupergraphRequestConf", + "description": "#/definitions/SupergraphRequestConf" + }, + "response": { + "$ref": "#/definitions/SupergraphResponseConf", + "description": "#/definitions/SupergraphResponseConf" + } + }, + "type": "object" + }, + "Temporality": { + "oneOf": [ + { + "description": "Export cumulative metrics.", + "enum": [ + "cumulative" + ], + "type": "string" + }, + { + "description": "Export delta metrics. `Delta` should be used when exporting to DataDog Agent.", + "enum": [ + "delta" + ], + "type": "string" + } + ] + }, + "TestError": { + "enum": [ + "estimated_cost_too_expensive", + "actual_cost_too_expensive" + ], + "type": "string" + }, + "TestStage": { + "enum": [ + "execution_request", + "execution_response", + "subgraph_request", + "subgraph_response" + ], + "type": "string" + }, + "Tls": { + "additionalProperties": false, + "description": "TLS related configuration options.", + "properties": { + "subgraph": { + "$ref": "#/definitions/SubgraphConfiguration_for_TlsClient", + "description": "#/definitions/SubgraphConfiguration_for_TlsClient" + }, + "supergraph": { + "$ref": "#/definitions/TlsSupergraph", + "description": "#/definitions/TlsSupergraph", + "nullable": true + } + }, + "type": "object" + }, + "TlsClient": { + "additionalProperties": false, + "description": "Configuration options pertaining to the subgraph server component.", + "properties": { + "certificate_authorities": { + "description": "list of certificate authorities in PEM format", + "nullable": true, + "type": "string" + }, + "client_authentication": { + "$ref": "#/definitions/TlsClientAuth", + "description": "#/definitions/TlsClientAuth", + "nullable": true + } + }, + "type": "object" + }, + "TlsClientAuth": { + "additionalProperties": false, + "description": "TLS client authentication", + "properties": { + "certificate_chain": { + "description": "list of certificates in PEM format", + "type": "string", + "writeOnly": true + }, + "key": { + "description": "key in PEM format", + "type": "string", + "writeOnly": true + } + }, + "required": [ + "certificate_chain", + "key" + ], + "type": "object" + }, + "TlsSupergraph": { + "additionalProperties": false, + "description": "Configuration options pertaining to the supergraph server component.", + "properties": { + "certificate": { + "description": "server certificate in PEM format", + "type": "string", + "writeOnly": true + }, + "certificate_chain": { + "description": "list of certificate authorities in PEM format", + "type": "string", + "writeOnly": true + }, + "key": { + "description": "server key in PEM format", + "type": "string", + "writeOnly": true + } + }, + "required": [ + "certificate", + "certificate_chain", + "key" + ], + "type": "object" + }, + "TraceIdFormat": { + "oneOf": [ + { + "description": "Format the Trace ID as a hexadecimal number\n\n(e.g. Trace ID 16 -> 00000000000000000000000000000010)", + "enum": [ + "hexadecimal" + ], + "type": "string" + }, + { + "description": "Format the Trace ID as a decimal number\n\n(e.g. Trace ID 16 -> 16)", + "enum": [ + "decimal" + ], + "type": "string" + } + ] + }, + "TraceIdFormat2": { + "oneOf": [ + { + "description": "Open Telemetry trace ID, a hex string.", + "enum": [ + "open_telemetry" + ], + "type": "string" + }, + { + "description": "Datadog trace ID, a u64.", + "enum": [ + "datadog" + ], + "type": "string" + } + ] + }, + "Tracing": { + "additionalProperties": false, + "description": "Tracing configuration", + "properties": { + "common": { + "$ref": "#/definitions/TracingCommon", + "description": "#/definitions/TracingCommon" + }, + "datadog": { + "$ref": "#/definitions/Config13", + "description": "#/definitions/Config13" + }, + "experimental_response_trace_id": { + "$ref": "#/definitions/ExposeTraceId", + "description": "#/definitions/ExposeTraceId" + }, + "jaeger": { + "$ref": "#/definitions/Config11", + "description": "#/definitions/Config11" + }, + "otlp": { + "$ref": "#/definitions/Config9", + "description": "#/definitions/Config9" + }, + "propagation": { + "$ref": "#/definitions/Propagation", + "description": "#/definitions/Propagation" + }, + "zipkin": { + "$ref": "#/definitions/Config12", + "description": "#/definitions/Config12" + } + }, + "type": "object" + }, + "TracingCommon": { + "additionalProperties": false, + "properties": { + "max_attributes_per_event": { + "default": 128, + "description": "The maximum attributes per event before discarding", + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "max_attributes_per_link": { + "default": 128, + "description": "The maximum attributes per link before discarding", + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "max_attributes_per_span": { + "default": 128, + "description": "The maximum attributes per span before discarding", + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "max_events_per_span": { + "default": 128, + "description": "The maximum events per span before discarding", + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "max_links_per_span": { + "default": 128, + "description": "The maximum links per span before discarding", + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "parent_based_sampler": { + "default": true, + "description": "Whether to use parent based sampling", + "type": "boolean" + }, + "resource": { + "additionalProperties": { + "$ref": "#/definitions/AttributeValue", + "description": "#/definitions/AttributeValue" + }, + "default": {}, + "description": "The Open Telemetry resource", + "type": "object" + }, + "sampler": { + "$ref": "#/definitions/SamplerOption", + "description": "#/definitions/SamplerOption" + }, + "service_name": { + "description": "The trace service name", + "nullable": true, + "type": "string" + }, + "service_namespace": { + "description": "The trace service namespace", + "nullable": true, + "type": "string" + } + }, + "type": "object" + }, + "Ttl": { + "description": "Per subgraph configuration for entity caching", + "type": "string" + }, + "UriEndpoint": { + "type": "string" + }, + "WebSocketConfiguration": { + "additionalProperties": false, + "description": "WebSocket configuration for a specific subgraph", + "properties": { + "heartbeat_interval": { + "$ref": "#/definitions/HeartbeatInterval", + "description": "#/definitions/HeartbeatInterval" + }, + "path": { + "description": "Path on which WebSockets are listening", + "nullable": true, + "type": "string" + }, + "protocol": { + "$ref": "#/definitions/WebSocketProtocol", + "description": "#/definitions/WebSocketProtocol" + } + }, + "type": "object" + }, + "WebSocketProtocol": { + "enum": [ + "graphql_ws", + "graphql_transport_ws" + ], + "type": "string" + }, + "conditional_attribute_apollo_router::plugins::telemetry::config_new::selectors::RouterSelector": { + "anyOf": [ + { + "$ref": "#/definitions/RouterSelector", + "description": "#/definitions/RouterSelector" + }, + { + "properties": { + "condition": { + "$ref": "#/definitions/Condition_for_RouterSelector", + "description": "#/definitions/Condition_for_RouterSelector" + } + } + } + ] + }, + "conditional_attribute_apollo_router::plugins::telemetry::config_new::selectors::SubgraphSelector": { + "anyOf": [ + { + "$ref": "#/definitions/SubgraphSelector", + "description": "#/definitions/SubgraphSelector" + }, + { + "properties": { + "condition": { + "$ref": "#/definitions/Condition_for_SubgraphSelector", + "description": "#/definitions/Condition_for_SubgraphSelector" + } + } + } + ] + }, + "conditional_attribute_apollo_router::plugins::telemetry::config_new::selectors::SupergraphSelector": { + "anyOf": [ + { + "$ref": "#/definitions/SupergraphSelector", + "description": "#/definitions/SupergraphSelector" + }, + { + "properties": { + "condition": { + "$ref": "#/definitions/Condition_for_SupergraphSelector", + "description": "#/definitions/Condition_for_SupergraphSelector" + } + } + } + ] + }, + "extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::RouterAttributes_apollo_router::plugins::telemetry::config_new::conditional::Conditional": { + "additionalProperties": { + "$ref": "#/definitions/conditional_attribute_apollo_router::plugins::telemetry::config_new::selectors::RouterSelector", + "description": "#/definitions/conditional_attribute_apollo_router::plugins::telemetry::config_new::selectors::RouterSelector" + }, + "description": "Common attributes for http server and client. See https://opentelemetry.io/docs/specs/semconv/http/http-spans/#common-attributes", + "properties": { + "baggage": { + "description": "All key values from trace baggage.", + "nullable": true, + "type": "boolean" + }, + "dd.trace_id": { + "description": "The datadog trace ID. This can be output in logs and used to correlate traces in Datadog.", + "nullable": true, + "type": "boolean" + }, + "error.type": { + "description": "Describes a class of error the operation ended with. Examples: * timeout * name_resolution_error * 500 Requirement level: Conditionally Required: If request has ended with an error.", + "nullable": true, + "type": "boolean" + }, + "http.request.body.size": { + "description": "The size of the request payload body in bytes. This is the number of bytes transferred excluding headers and is often, but not always, present as the Content-Length header. For requests using transport encoding, this should be the compressed size. Examples: * 3495 Requirement level: Recommended", + "nullable": true, + "type": "boolean" + }, + "http.request.method": { + "description": "HTTP request method. Examples: * GET * POST * HEAD Requirement level: Required", + "nullable": true, + "type": "boolean" + }, + "http.response.body.size": { + "description": "The size of the response payload body in bytes. This is the number of bytes transferred excluding headers and is often, but not always, present as the Content-Length header. For requests using transport encoding, this should be the compressed size. Examples: * 3495 Requirement level: Recommended", + "nullable": true, + "type": "boolean" + }, + "http.response.status_code": { + "description": "HTTP response status code. Examples: * 200 Requirement level: Conditionally Required: If and only if one was received/sent.", + "nullable": true, + "type": "boolean" + }, + "http.route": { + "description": "The matched route (path template in the format used by the respective server framework). Examples: * /graphql Requirement level: Conditionally Required: If and only if it’s available", + "nullable": true, + "type": "boolean" + }, + "network.local.address": { + "description": "Local socket address. Useful in case of a multi-IP host. Examples: * 10.1.2.80 * /tmp/my.sock Requirement level: Opt-In", + "nullable": true, + "type": "boolean" + }, + "network.local.port": { + "description": "Local socket port. Useful in case of a multi-port host. Examples: * 65123 Requirement level: Opt-In", + "nullable": true, + "type": "boolean" + }, + "network.peer.address": { + "description": "Peer address of the network connection - IP address or Unix domain socket name. Examples: * 10.1.2.80 * /tmp/my.sock Requirement level: Recommended", + "nullable": true, + "type": "boolean" + }, + "network.peer.port": { + "description": "Peer port number of the network connection. Examples: * 65123 Requirement level: Recommended", + "nullable": true, + "type": "boolean" + }, + "network.protocol.name": { + "description": "OSI application layer or non-OSI equivalent. Examples: * http * spdy Requirement level: Recommended: if not default (http).", + "nullable": true, + "type": "boolean" + }, + "network.protocol.version": { + "description": "Version of the protocol specified in network.protocol.name. Examples: * 1.0 * 1.1 * 2 * 3 Requirement level: Recommended", + "nullable": true, + "type": "boolean" + }, + "network.transport": { + "description": "OSI transport layer. Examples: * tcp * udp Requirement level: Conditionally Required", + "nullable": true, + "type": "boolean" + }, + "network.type": { + "description": "OSI network layer or non-OSI equivalent. Examples: * ipv4 * ipv6 Requirement level: Recommended", + "nullable": true, + "type": "boolean" + }, + "server.address": { + "description": "Name of the local HTTP server that received the request. Examples: * example.com * 10.1.2.80 * /tmp/my.sock Requirement level: Recommended", + "nullable": true, + "type": "boolean" + }, + "server.port": { + "description": "Port of the local HTTP server that received the request. Examples: * 80 * 8080 * 443 Requirement level: Recommended", + "nullable": true, + "type": "boolean" + }, + "trace_id": { + "description": "The OpenTelemetry trace ID. This can be output in logs.", + "nullable": true, + "type": "boolean" + }, + "url.path": { + "description": "The URI path component Examples: * /search Requirement level: Required", + "nullable": true, + "type": "boolean" + }, + "url.query": { + "description": "The URI query component Examples: * q=OpenTelemetry Requirement level: Conditionally Required: If and only if one was received/sent.", + "nullable": true, + "type": "boolean" + }, + "url.scheme": { + "description": "The URI scheme component identifying the used protocol. Examples: * http * https Requirement level: Required", + "nullable": true, + "type": "boolean" + }, + "user_agent.original": { + "description": "Value of the HTTP User-Agent header sent by the client. Examples: * CERN-LineMode/2.15 * libwww/2.17b3 Requirement level: Recommended", + "nullable": true, + "type": "boolean" + } + }, + "type": "object" + }, + "extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::RouterAttributes_apollo_router::plugins::telemetry::config_new::selectors::RouterSelector": { + "additionalProperties": { + "$ref": "#/definitions/RouterSelector", + "description": "#/definitions/RouterSelector" + }, + "description": "Common attributes for http server and client. See https://opentelemetry.io/docs/specs/semconv/http/http-spans/#common-attributes", + "properties": { + "baggage": { + "description": "All key values from trace baggage.", + "nullable": true, + "type": "boolean" + }, + "dd.trace_id": { + "description": "The datadog trace ID. This can be output in logs and used to correlate traces in Datadog.", + "nullable": true, + "type": "boolean" + }, + "error.type": { + "description": "Describes a class of error the operation ended with. Examples: * timeout * name_resolution_error * 500 Requirement level: Conditionally Required: If request has ended with an error.", + "nullable": true, + "type": "boolean" + }, + "http.request.body.size": { + "description": "The size of the request payload body in bytes. This is the number of bytes transferred excluding headers and is often, but not always, present as the Content-Length header. For requests using transport encoding, this should be the compressed size. Examples: * 3495 Requirement level: Recommended", + "nullable": true, + "type": "boolean" + }, + "http.request.method": { + "description": "HTTP request method. Examples: * GET * POST * HEAD Requirement level: Required", + "nullable": true, + "type": "boolean" + }, + "http.response.body.size": { + "description": "The size of the response payload body in bytes. This is the number of bytes transferred excluding headers and is often, but not always, present as the Content-Length header. For requests using transport encoding, this should be the compressed size. Examples: * 3495 Requirement level: Recommended", + "nullable": true, + "type": "boolean" + }, + "http.response.status_code": { + "description": "HTTP response status code. Examples: * 200 Requirement level: Conditionally Required: If and only if one was received/sent.", + "nullable": true, + "type": "boolean" + }, + "http.route": { + "description": "The matched route (path template in the format used by the respective server framework). Examples: * /graphql Requirement level: Conditionally Required: If and only if it’s available", + "nullable": true, + "type": "boolean" + }, + "network.local.address": { + "description": "Local socket address. Useful in case of a multi-IP host. Examples: * 10.1.2.80 * /tmp/my.sock Requirement level: Opt-In", + "nullable": true, + "type": "boolean" + }, + "network.local.port": { + "description": "Local socket port. Useful in case of a multi-port host. Examples: * 65123 Requirement level: Opt-In", + "nullable": true, + "type": "boolean" + }, + "network.peer.address": { + "description": "Peer address of the network connection - IP address or Unix domain socket name. Examples: * 10.1.2.80 * /tmp/my.sock Requirement level: Recommended", + "nullable": true, + "type": "boolean" + }, + "network.peer.port": { + "description": "Peer port number of the network connection. Examples: * 65123 Requirement level: Recommended", + "nullable": true, + "type": "boolean" + }, + "network.protocol.name": { + "description": "OSI application layer or non-OSI equivalent. Examples: * http * spdy Requirement level: Recommended: if not default (http).", + "nullable": true, + "type": "boolean" + }, + "network.protocol.version": { + "description": "Version of the protocol specified in network.protocol.name. Examples: * 1.0 * 1.1 * 2 * 3 Requirement level: Recommended", + "nullable": true, + "type": "boolean" + }, + "network.transport": { + "description": "OSI transport layer. Examples: * tcp * udp Requirement level: Conditionally Required", + "nullable": true, + "type": "boolean" + }, + "network.type": { + "description": "OSI network layer or non-OSI equivalent. Examples: * ipv4 * ipv6 Requirement level: Recommended", + "nullable": true, + "type": "boolean" + }, + "server.address": { + "description": "Name of the local HTTP server that received the request. Examples: * example.com * 10.1.2.80 * /tmp/my.sock Requirement level: Recommended", + "nullable": true, + "type": "boolean" + }, + "server.port": { + "description": "Port of the local HTTP server that received the request. Examples: * 80 * 8080 * 443 Requirement level: Recommended", + "nullable": true, + "type": "boolean" + }, + "trace_id": { + "description": "The OpenTelemetry trace ID. This can be output in logs.", + "nullable": true, + "type": "boolean" + }, + "url.path": { + "description": "The URI path component Examples: * /search Requirement level: Required", + "nullable": true, + "type": "boolean" + }, + "url.query": { + "description": "The URI query component Examples: * q=OpenTelemetry Requirement level: Conditionally Required: If and only if one was received/sent.", + "nullable": true, + "type": "boolean" + }, + "url.scheme": { + "description": "The URI scheme component identifying the used protocol. Examples: * http * https Requirement level: Required", + "nullable": true, + "type": "boolean" + }, + "user_agent.original": { + "description": "Value of the HTTP User-Agent header sent by the client. Examples: * CERN-LineMode/2.15 * libwww/2.17b3 Requirement level: Recommended", + "nullable": true, + "type": "boolean" + } + }, + "type": "object" + }, + "extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SubgraphAttributes_apollo_router::plugins::telemetry::config_new::conditional::Conditional": { + "additionalProperties": { + "$ref": "#/definitions/conditional_attribute_apollo_router::plugins::telemetry::config_new::selectors::SubgraphSelector", + "description": "#/definitions/conditional_attribute_apollo_router::plugins::telemetry::config_new::selectors::SubgraphSelector" + }, + "properties": { + "subgraph.graphql.document": { + "description": "The GraphQL document being executed. Examples: * query findBookById { bookById(id: ?) { name } } Requirement level: Recommended", + "nullable": true, + "type": "boolean" + }, + "subgraph.graphql.operation.name": { + "description": "The name of the operation being executed. Examples: * findBookById Requirement level: Recommended", + "nullable": true, + "type": "boolean" + }, + "subgraph.graphql.operation.type": { + "description": "The type of the operation being executed. Examples: * query * subscription * mutation Requirement level: Recommended", + "nullable": true, + "type": "boolean" + }, + "subgraph.name": { + "description": "The name of the subgraph Examples: * products Requirement level: Required", + "nullable": true, + "type": "boolean" + } + }, + "type": "object" + }, + "extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SubgraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SubgraphSelector": { + "additionalProperties": { + "$ref": "#/definitions/SubgraphSelector", + "description": "#/definitions/SubgraphSelector" + }, + "properties": { + "subgraph.graphql.document": { + "description": "The GraphQL document being executed. Examples: * query findBookById { bookById(id: ?) { name } } Requirement level: Recommended", + "nullable": true, + "type": "boolean" + }, + "subgraph.graphql.operation.name": { + "description": "The name of the operation being executed. Examples: * findBookById Requirement level: Recommended", + "nullable": true, + "type": "boolean" + }, + "subgraph.graphql.operation.type": { + "description": "The type of the operation being executed. Examples: * query * subscription * mutation Requirement level: Recommended", + "nullable": true, + "type": "boolean" + }, + "subgraph.name": { + "description": "The name of the subgraph Examples: * products Requirement level: Required", + "nullable": true, + "type": "boolean" + } + }, + "type": "object" + }, + "extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SupergraphAttributes_apollo_router::plugins::telemetry::config_new::conditional::Conditional": { + "additionalProperties": { + "$ref": "#/definitions/conditional_attribute_apollo_router::plugins::telemetry::config_new::selectors::SupergraphSelector", + "description": "#/definitions/conditional_attribute_apollo_router::plugins::telemetry::config_new::selectors::SupergraphSelector" + }, + "description": "Attributes for Cost", + "properties": { + "cost.actual": { + "description": "The actual cost of the operation using the currently configured cost model", + "nullable": true, + "type": "boolean" + }, + "cost.delta": { + "description": "The delta (estimated - actual) cost of the operation using the currently configured cost model", + "nullable": true, + "type": "boolean" + }, + "cost.estimated": { + "description": "The estimated cost of the operation using the currently configured cost model", + "nullable": true, + "type": "boolean" + }, + "cost.result": { + "description": "The cost result, this is an error code returned by the cost calculation or COST_OK", + "nullable": true, + "type": "boolean" + }, + "graphql.document": { + "description": "The GraphQL document being executed. Examples: * query findBookById { bookById(id: ?) { name } } Requirement level: Recommended", + "nullable": true, + "type": "boolean" + }, + "graphql.operation.name": { + "description": "The name of the operation being executed. Examples: * findBookById Requirement level: Recommended", + "nullable": true, + "type": "boolean" + }, + "graphql.operation.type": { + "description": "The type of the operation being executed. Examples: * query * subscription * mutation Requirement level: Recommended", + "nullable": true, + "type": "boolean" + } + }, + "type": "object" + }, + "extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SupergraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SupergraphSelector": { + "additionalProperties": { + "$ref": "#/definitions/SupergraphSelector", + "description": "#/definitions/SupergraphSelector" + }, + "description": "Attributes for Cost", + "properties": { + "cost.actual": { + "description": "The actual cost of the operation using the currently configured cost model", + "nullable": true, + "type": "boolean" + }, + "cost.delta": { + "description": "The delta (estimated - actual) cost of the operation using the currently configured cost model", + "nullable": true, + "type": "boolean" + }, + "cost.estimated": { + "description": "The estimated cost of the operation using the currently configured cost model", + "nullable": true, + "type": "boolean" + }, + "cost.result": { + "description": "The cost result, this is an error code returned by the cost calculation or COST_OK", + "nullable": true, + "type": "boolean" + }, + "graphql.document": { + "description": "The GraphQL document being executed. Examples: * query findBookById { bookById(id: ?) { name } } Requirement level: Recommended", + "nullable": true, + "type": "boolean" + }, + "graphql.operation.name": { + "description": "The name of the operation being executed. Examples: * findBookById Requirement level: Recommended", + "nullable": true, + "type": "boolean" + }, + "graphql.operation.type": { + "description": "The type of the operation being executed. Examples: * query * subscription * mutation Requirement level: Recommended", + "nullable": true, + "type": "boolean" + } + }, + "type": "object" + }, + "extendable_attribute_apollo_router::plugins::telemetry::config_new::events::RouterEventsConfig_apollo_router::plugins::telemetry::config_new::events::Event": { + "additionalProperties": { + "$ref": "#/definitions/Event_for_RouterAttributes_and_RouterSelector", + "description": "#/definitions/Event_for_RouterAttributes_and_RouterSelector" + }, + "properties": { + "error": { + "$ref": "#/definitions/EventLevel", + "description": "#/definitions/EventLevel" + }, + "request": { + "$ref": "#/definitions/EventLevel", + "description": "#/definitions/EventLevel" + }, + "response": { + "$ref": "#/definitions/EventLevel", + "description": "#/definitions/EventLevel" + } + }, + "type": "object" + }, + "extendable_attribute_apollo_router::plugins::telemetry::config_new::events::SubgraphEventsConfig_apollo_router::plugins::telemetry::config_new::events::Event": { + "additionalProperties": { + "$ref": "#/definitions/Event_for_SubgraphAttributes_and_SubgraphSelector", + "description": "#/definitions/Event_for_SubgraphAttributes_and_SubgraphSelector" + }, + "properties": { + "error": { + "$ref": "#/definitions/EventLevel", + "description": "#/definitions/EventLevel" + }, + "request": { + "$ref": "#/definitions/EventLevel", + "description": "#/definitions/EventLevel" + }, + "response": { + "$ref": "#/definitions/EventLevel", + "description": "#/definitions/EventLevel" + } + }, + "type": "object" + }, + "extendable_attribute_apollo_router::plugins::telemetry::config_new::events::SupergraphEventsConfig_apollo_router::plugins::telemetry::config_new::events::Event": { + "additionalProperties": { + "$ref": "#/definitions/Event_for_SupergraphAttributes_and_SupergraphSelector", + "description": "#/definitions/Event_for_SupergraphAttributes_and_SupergraphSelector" + }, + "properties": { + "error": { + "$ref": "#/definitions/EventLevel", + "description": "#/definitions/EventLevel" + }, + "request": { + "$ref": "#/definitions/EventLevel", + "description": "#/definitions/EventLevel" + }, + "response": { + "$ref": "#/definitions/EventLevel", + "description": "#/definitions/EventLevel" + } + }, + "type": "object" + }, + "extendable_attribute_apollo_router::plugins::telemetry::config_new::instruments::RouterInstrumentsConfig_apollo_router::plugins::telemetry::config_new::instruments::Instrument": { + "additionalProperties": { + "$ref": "#/definitions/Instrument_for_RouterAttributes_and_RouterSelector", + "description": "#/definitions/Instrument_for_RouterAttributes_and_RouterSelector" + }, + "properties": { + "http.server.active_requests": { + "$ref": "#/definitions/DefaultedStandardInstrument_for_ActiveRequestsAttributes", + "description": "#/definitions/DefaultedStandardInstrument_for_ActiveRequestsAttributes" + }, + "http.server.request.body.size": { + "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::RouterAttributes_apollo_router::plugins::telemetry::config_new::selectors::RouterSelector", + "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::RouterAttributes_apollo_router::plugins::telemetry::config_new::selectors::RouterSelector" + }, + "http.server.request.duration": { + "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::RouterAttributes_apollo_router::plugins::telemetry::config_new::selectors::RouterSelector", + "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::RouterAttributes_apollo_router::plugins::telemetry::config_new::selectors::RouterSelector" + }, + "http.server.response.body.size": { + "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::RouterAttributes_apollo_router::plugins::telemetry::config_new::selectors::RouterSelector", + "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::RouterAttributes_apollo_router::plugins::telemetry::config_new::selectors::RouterSelector" + } + }, + "type": "object" + }, + "extendable_attribute_apollo_router::plugins::telemetry::config_new::instruments::SubgraphInstrumentsConfig_apollo_router::plugins::telemetry::config_new::instruments::Instrument": { + "additionalProperties": { + "$ref": "#/definitions/Instrument_for_SubgraphAttributes_and_SubgraphSelector", + "description": "#/definitions/Instrument_for_SubgraphAttributes_and_SubgraphSelector" + }, + "properties": { + "http.client.request.body.size": { + "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SubgraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SubgraphSelector", + "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SubgraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SubgraphSelector" + }, + "http.client.request.duration": { + "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SubgraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SubgraphSelector", + "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SubgraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SubgraphSelector" + }, + "http.client.response.body.size": { + "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SubgraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SubgraphSelector", + "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SubgraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SubgraphSelector" + } + }, + "type": "object" + }, + "extendable_attribute_apollo_router::plugins::telemetry::config_new::instruments::SupergraphInstrumentsConfig_apollo_router::plugins::telemetry::config_new::instruments::Instrument": { + "additionalProperties": { + "$ref": "#/definitions/Instrument_for_SupergraphAttributes_and_SupergraphSelector", + "description": "#/definitions/Instrument_for_SupergraphAttributes_and_SupergraphSelector" + }, + "properties": { + "cost.actual": { + "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SupergraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SupergraphSelector", + "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SupergraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SupergraphSelector" + }, + "cost.delta": { + "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SupergraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SupergraphSelector", + "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SupergraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SupergraphSelector" + }, + "cost.estimated": { + "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SupergraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SupergraphSelector", + "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SupergraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SupergraphSelector" + } + }, + "type": "object" + }, + "logging_format": { + "oneOf": [ + { + "additionalProperties": false, + "description": "Tracing subscriber https://docs.rs/tracing-subscriber/latest/tracing_subscriber/fmt/format/struct.Json.html", + "properties": { + "json": { + "additionalProperties": false, + "properties": { + "display_current_span": { + "default": false, + "description": "Include the current span in this log event.", + "type": "boolean" }, - { - "type": "object", - "required": [ - "supergraph_operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_operation_name": { - "description": "The supergraph query operation name.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false + "display_filename": { + "default": false, + "description": "Include the filename with the log event.", + "type": "boolean" }, - { - "type": "object", - "required": [ - "supergraph_operation_kind" - ], - "properties": { - "supergraph_operation_kind": { - "description": "The supergraph query operation kind (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false + "display_level": { + "default": true, + "description": "Include the level with the log event. (default: true)", + "type": "boolean" }, - { - "type": "object", - "required": [ - "supergraph_query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_query": { - "description": "The supergraph query to the subgraph.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false + "display_line_number": { + "default": false, + "description": "Include the line number with the log event.", + "type": "boolean" }, - { - "type": "object", - "required": [ - "supergraph_query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "supergraph_query_variable": { - "description": "The supergraph query variable name.", - "type": "string" - } - }, - "additionalProperties": false + "display_resource": { + "default": true, + "description": "Include the resource with the log event. (default: true)", + "type": "boolean" }, - { - "type": "object", - "required": [ - "supergraph_request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "supergraph_request_header": { - "description": "The supergraph request header name.", - "type": "string" - } - }, - "additionalProperties": false + "display_span_id": { + "default": true, + "description": "Include the span id (if any) with the log event. (default: true)", + "type": "boolean" }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false + "display_span_list": { + "default": true, + "description": "Include all of the containing span information with the log event. (default: true)", + "type": "boolean" }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false + "display_target": { + "default": true, + "description": "Include the target with the log event. (default: true)", + "type": "boolean" }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false + "display_thread_id": { + "default": false, + "description": "Include the thread_id with the log event.", + "type": "boolean" }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false + "display_thread_name": { + "default": false, + "description": "Include the thread_name with the log event.", + "type": "boolean" }, - { - "type": "string" + "display_timestamp": { + "default": true, + "description": "Include the timestamp with the log event. (default: true)", + "type": "boolean" }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false + "display_trace_id": { + "default": true, + "description": "Include the trace id (if any) with the log event. (default: true)", + "type": "boolean" } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } + }, + "type": "object" } }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", "required": [ - "not" + "json" ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_SubgraphSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] + "type": "object" }, { - "description": "Static false condition", - "type": "string", + "description": "Tracing subscriber https://docs.rs/tracing-subscriber/latest/tracing_subscriber/fmt/format/struct.Json.html", "enum": [ - "false" - ] - } - ] - }, - "Condition_for_SupergraphSelector": { - "oneOf": [ - { - "description": "A condition to check a selection against a value.", - "type": "object", - "required": [ - "eq" + "json" ], - "properties": { - "eq": { - "type": "array", - "items": { - "anyOf": [ - { - "description": "A constant value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ] - }, - { - "description": "Selector to extract a value from the pipeline.", - "anyOf": [ - { - "type": "object", - "required": [ - "operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "operation_name": { - "description": "The operation name from the query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "operation_kind" - ], - "properties": { - "operation_kind": { - "description": "The operation kind from the query (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "query": { - "description": "The graphql query.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "query_variable": { - "description": "The name of a graphql query variable.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "response_header": { - "description": "The name of the response header.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Cost attributes", - "type": "object", - "required": [ - "cost" - ], - "properties": { - "cost": { - "description": "The cost value to select, one of: estimated, actual, delta.", - "oneOf": [ - { - "description": "The estimated cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "estimated" - ] - }, - { - "description": "The actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "actual" - ] - }, - { - "description": "The delta between the estimated and actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "delta" - ] - }, - { - "description": "The result of the cost calculation. This is the error code returned by the cost calculation.", - "type": "string", - "enum": [ - "result" - ] - } - ] - } - }, - "additionalProperties": false - } - ] - } - ] - }, - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false + "type": "string" }, { - "description": "A condition to check a selection against a selector.", - "type": "object", - "required": [ - "exists" - ], + "additionalProperties": false, + "description": "Tracing subscriber https://docs.rs/tracing-subscriber/latest/tracing_subscriber/fmt/format/struct.Full.html", "properties": { - "exists": { - "anyOf": [ - { - "type": "object", - "required": [ - "operation_name" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "operation_name": { - "description": "The operation name from the query.", - "oneOf": [ - { - "description": "The raw operation name.", - "type": "string", - "enum": [ - "string" - ] - }, - { - "description": "A hash of the operation name.", - "type": "string", - "enum": [ - "hash" - ] - } - ] - } - }, - "additionalProperties": false + "text": { + "additionalProperties": false, + "properties": { + "ansi_escape_codes": { + "default": true, + "description": "Process ansi escapes (default: true)", + "type": "boolean" }, - { - "type": "object", - "required": [ - "operation_kind" - ], - "properties": { - "operation_kind": { - "description": "The operation kind from the query (query|mutation|subscription).", - "oneOf": [ - { - "description": "The raw operation kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false + "display_current_span": { + "default": true, + "description": "Include the current span in this log event. (default: true)", + "type": "boolean" }, - { - "type": "object", - "required": [ - "query" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "query": { - "description": "The graphql query.", - "oneOf": [ - { - "description": "The raw query kind.", - "type": "string", - "enum": [ - "string" - ] - } - ] - } - }, - "additionalProperties": false + "display_filename": { + "default": false, + "description": "Include the filename with the log event.", + "type": "boolean" }, - { - "type": "object", - "required": [ - "query_variable" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "query_variable": { - "description": "The name of a graphql query variable.", - "type": "string" - } - }, - "additionalProperties": false + "display_level": { + "default": true, + "description": "Include the level with the log event. (default: true)", + "type": "boolean" }, - { - "type": "object", - "required": [ - "request_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "request_header": { - "description": "The name of the request header.", - "type": "string" - } - }, - "additionalProperties": false + "display_line_number": { + "default": false, + "description": "Include the line number with the log event.", + "type": "boolean" }, - { - "type": "object", - "required": [ - "response_header" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "response_header": { - "description": "The name of the response header.", - "type": "string" - } - }, - "additionalProperties": false + "display_resource": { + "default": false, + "description": "Include the resource with the log event.", + "type": "boolean" }, - { - "description": "A status from the response", - "type": "object", - "required": [ - "response_status" - ], - "properties": { - "response_status": { - "description": "The http response status code.", - "oneOf": [ - { - "description": "The http status code.", - "type": "string", - "enum": [ - "code" - ] - }, - { - "description": "The http status reason.", - "type": "string", - "enum": [ - "reason" - ] - } - ] - } - }, - "additionalProperties": false + "display_service_name": { + "default": false, + "description": "Include the service name with the log event.", + "type": "boolean" }, - { - "type": "object", - "required": [ - "request_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "request_context": { - "description": "The request context key.", - "type": "string" - } - }, - "additionalProperties": false + "display_service_namespace": { + "default": false, + "description": "Include the service namespace with the log event.", + "type": "boolean" }, - { - "type": "object", - "required": [ - "response_context" - ], - "properties": { - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - }, - "response_context": { - "description": "The response context key.", - "type": "string" - } - }, - "additionalProperties": false + "display_span_id": { + "default": false, + "description": "Include the span id (if any) with the log event. (default: false)", + "type": "boolean" }, - { - "type": "object", - "required": [ - "baggage" - ], - "properties": { - "baggage": { - "description": "The name of the baggage item.", - "type": "string" - }, - "default": { - "description": "Optional default value.", - "anyOf": [ - { - "description": "bool values", - "type": "boolean" - }, - { - "description": "i64 values", - "type": "integer", - "format": "int64" - }, - { - "description": "f64 values", - "type": "number", - "format": "double" - }, - { - "description": "String values", - "type": "string" - }, - { - "description": "Array of homogeneous values", - "anyOf": [ - { - "description": "Array of bools", - "type": "array", - "items": { - "type": "boolean" - } - }, - { - "description": "Array of integers", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "Array of floats", - "type": "array", - "items": { - "type": "number", - "format": "double" - } - }, - { - "description": "Array of strings", - "type": "array", - "items": { - "type": "string" - } - } - ] - } - ], - "nullable": true - } - }, - "additionalProperties": false + "display_span_list": { + "default": true, + "description": "Include all of the containing span information with the log event. (default: true)", + "type": "boolean" }, - { - "type": "object", - "required": [ - "env" - ], - "properties": { - "default": { - "description": "Optional default value.", - "type": "string", - "nullable": true - }, - "env": { - "description": "The name of the environment variable", - "type": "string" - } - }, - "additionalProperties": false + "display_target": { + "default": false, + "description": "Include the target with the log event.", + "type": "boolean" }, - { - "type": "string" + "display_thread_id": { + "default": false, + "description": "Include the thread_id with the log event.", + "type": "boolean" }, - { - "type": "object", - "required": [ - "static" - ], - "properties": { - "static": { - "description": "A static string value", - "type": "string" - } - }, - "additionalProperties": false + "display_thread_name": { + "default": false, + "description": "Include the thread_name with the log event.", + "type": "boolean" }, - { - "description": "Cost attributes", - "type": "object", - "required": [ - "cost" - ], - "properties": { - "cost": { - "description": "The cost value to select, one of: estimated, actual, delta.", - "oneOf": [ - { - "description": "The estimated cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "estimated" - ] - }, - { - "description": "The actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "actual" - ] - }, - { - "description": "The delta between the estimated and actual cost of the operation using the currently configured cost model", - "type": "string", - "enum": [ - "delta" - ] - }, - { - "description": "The result of the cost calculation. This is the error code returned by the cost calculation.", - "type": "string", - "enum": [ - "result" - ] - } - ] - } - }, - "additionalProperties": false + "display_timestamp": { + "default": true, + "description": "Include the timestamp with the log event. (default: true)", + "type": "boolean" + }, + "display_trace_id": { + "default": false, + "description": "Include the trace id (if any) with the log event. (default: false)", + "type": "boolean" } - ] - } - }, - "additionalProperties": false - }, - { - "description": "All sub-conditions must be true.", - "type": "object", - "required": [ - "all" - ], - "properties": { - "all": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SupergraphSelector" - } - } - }, - "additionalProperties": false - }, - { - "description": "At least one sub-conditions must be true.", - "type": "object", - "required": [ - "any" - ], - "properties": { - "any": { - "type": "array", - "items": { - "$ref": "#/definitions/Condition_for_SupergraphSelector" - } + }, + "type": "object" } }, - "additionalProperties": false - }, - { - "description": "The sub-condition must not be true", - "type": "object", "required": [ - "not" + "text" ], - "properties": { - "not": { - "$ref": "#/definitions/Condition_for_SupergraphSelector" - } - }, - "additionalProperties": false - }, - { - "description": "Static true condition", - "type": "string", - "enum": [ - "true" - ] + "type": "object" }, { - "description": "Static false condition", - "type": "string", + "description": "Tracing subscriber https://docs.rs/tracing-subscriber/latest/tracing_subscriber/fmt/format/struct.Full.html", "enum": [ - "false" - ] + "text" + ], + "type": "string" } ] } - } + }, + "description": "The configuration for the router.\n\nCan be created through `serde::Deserialize` from various formats, or inline in Rust code with `serde_json::json!` and `serde_json::from_value`.", + "properties": { + "apq": { + "$ref": "#/definitions/Apq", + "description": "#/definitions/Apq" + }, + "authentication": { + "$ref": "#/definitions/Conf2", + "description": "#/definitions/Conf2" + }, + "authorization": { + "$ref": "#/definitions/Conf3", + "description": "#/definitions/Conf3" + }, + "batching": { + "$ref": "#/definitions/Batching", + "description": "#/definitions/Batching" + }, + "coprocessor": { + "$ref": "#/definitions/Conf4", + "description": "#/definitions/Conf4" + }, + "cors": { + "$ref": "#/definitions/Cors", + "description": "#/definitions/Cors" + }, + "csrf": { + "$ref": "#/definitions/CSRFConfig", + "description": "#/definitions/CSRFConfig" + }, + "experimental_api_schema_generation_mode": { + "$ref": "#/definitions/ApiSchemaMode", + "description": "#/definitions/ApiSchemaMode" + }, + "experimental_apollo_metrics_generation_mode": { + "$ref": "#/definitions/ApolloMetricsGenerationMode", + "description": "#/definitions/ApolloMetricsGenerationMode" + }, + "experimental_chaos": { + "$ref": "#/definitions/Chaos", + "description": "#/definitions/Chaos" + }, + "experimental_demand_control": { + "$ref": "#/definitions/DemandControlConfig", + "description": "#/definitions/DemandControlConfig" + }, + "experimental_query_planner_mode": { + "$ref": "#/definitions/QueryPlannerMode", + "description": "#/definitions/QueryPlannerMode" + }, + "experimental_type_conditioned_fetching": { + "default": false, + "description": "Type conditioned fetching configuration.", + "type": "boolean" + }, + "forbid_mutations": { + "$ref": "#/definitions/ForbidMutationsConfig", + "description": "#/definitions/ForbidMutationsConfig" + }, + "headers": { + "$ref": "#/definitions/Config4", + "description": "#/definitions/Config4" + }, + "health_check": { + "$ref": "#/definitions/HealthCheck", + "description": "#/definitions/HealthCheck" + }, + "homepage": { + "$ref": "#/definitions/Homepage", + "description": "#/definitions/Homepage" + }, + "include_subgraph_errors": { + "$ref": "#/definitions/Config5", + "description": "#/definitions/Config5" + }, + "limits": { + "$ref": "#/definitions/Limits", + "description": "#/definitions/Limits" + }, + "override_subgraph_url": { + "$ref": "#/definitions/Conf5", + "description": "#/definitions/Conf5" + }, + "persisted_queries": { + "$ref": "#/definitions/PersistedQueries", + "description": "#/definitions/PersistedQueries" + }, + "plugins": { + "$ref": "#/definitions/Plugins", + "description": "#/definitions/Plugins" + }, + "preview_entity_cache": { + "$ref": "#/definitions/Config6", + "description": "#/definitions/Config6" + }, + "preview_file_uploads": { + "$ref": "#/definitions/FileUploadsConfig", + "description": "#/definitions/FileUploadsConfig" + }, + "progressive_override": { + "$ref": "#/definitions/Config7", + "description": "#/definitions/Config7" + }, + "rhai": { + "$ref": "#/definitions/Conf6", + "description": "#/definitions/Conf6" + }, + "sandbox": { + "$ref": "#/definitions/Sandbox", + "description": "#/definitions/Sandbox" + }, + "subscription": { + "$ref": "#/definitions/SubscriptionConfig", + "description": "#/definitions/SubscriptionConfig" + }, + "supergraph": { + "$ref": "#/definitions/Supergraph", + "description": "#/definitions/Supergraph" + }, + "telemetry": { + "$ref": "#/definitions/Conf7", + "description": "#/definitions/Conf7" + }, + "tls": { + "$ref": "#/definitions/Tls", + "description": "#/definitions/Tls" + }, + "traffic_shaping": { + "$ref": "#/definitions/Config14", + "description": "#/definitions/Config14" + } + }, + "title": "Configuration", + "type": "object" } diff --git a/apollo-router/src/configuration/testdata/metrics/demand_control.router.yaml b/apollo-router/src/configuration/testdata/metrics/demand_control.router.yaml new file mode 100644 index 0000000000..2e479c1160 --- /dev/null +++ b/apollo-router/src/configuration/testdata/metrics/demand_control.router.yaml @@ -0,0 +1,7 @@ +experimental_demand_control: + enabled: true + mode: measure + strategy: + static_estimated: + list_size: 30 + max: 256 \ No newline at end of file diff --git a/apollo-router/src/configuration/tests.rs b/apollo-router/src/configuration/tests.rs index 49979be1de..3080a48ace 100644 --- a/apollo-router/src/configuration/tests.rs +++ b/apollo-router/src/configuration/tests.rs @@ -21,14 +21,16 @@ use crate::error::SchemaError; #[cfg(unix)] #[test] fn schema_generation() { - let settings = SchemaSettings::draft2019_09().with(|s| { - s.option_nullable = true; - s.option_add_null_type = false; - s.inline_subschemas = true; + let schema = generate_config_schema(); + insta::with_settings!({sort_maps => true}, { + assert_json_snapshot!(&schema) }); - let gen = settings.into_generator(); - let schema = gen.into_root_schema_for::(); - assert_json_snapshot!(&schema) + let json_schema = + serde_json::to_string_pretty(&schema).expect("must be able to deserialize schema"); + assert!( + json_schema.len() < 500 * 1024, + "schema must be less than 500kb" + ); } #[test] @@ -691,12 +693,16 @@ fn visit_schema(path: &str, schema: &Value, errors: &mut Vec) { for (k, v) in properties { let path = format!("{path}.{k}"); if v.as_object().and_then(|o| o.get("description")).is_none() { - errors.push(format!("{path} was missing a description")); + // Enum type does not get a description + if k != "type" { + errors.push(format!("{path} was missing a description")); + } } visit_schema(&path, v, errors) } } else { - visit_schema(path, v, errors) + let path = format!("{path}.{k}"); + visit_schema(&path, v, errors) } } } diff --git a/apollo-router/src/context/mod.rs b/apollo-router/src/context/mod.rs index d9548b6d88..ef71f953ea 100644 --- a/apollo-router/src/context/mod.rs +++ b/apollo-router/src/context/mod.rs @@ -235,15 +235,28 @@ impl Context { } /// Notify the busy timer that we're waiting on a network request - pub(crate) fn enter_active_request(&self) -> BusyTimerGuard { + /// + /// When a plugin makes a network call that would block request handling, this + /// indicates to the processing time counter that it should stop measuring while + /// we wait for the call to finish. When the value returned by this method is + /// dropped, the router will start measuring again, unless we are still covered + /// by another active request (ex: parallel subgraph calls) + pub fn enter_active_request(&self) -> BusyTimerGuard { self.busy_timer.lock().increment_active_requests(); BusyTimerGuard { busy_timer: self.busy_timer.clone(), } } - /// How much time was spent working on the request - pub(crate) fn busy_time(&self) -> Duration { + /// Time actually spent working on this request + /// + /// This is the request duration without the time spent waiting for external calls + /// (coprocessor and subgraph requests). This metric is an approximation of + /// the time spent, because in the case of parallel subgraph calls, some + /// router processing time could happen during a network call (and so would + /// not be accounted for) and make another task late. + /// This is reported under the `apollo_router_processing_time` metric + pub fn busy_time(&self) -> Duration { self.busy_timer.lock().current() } @@ -264,7 +277,7 @@ impl Context { } } -pub(crate) struct BusyTimerGuard { +pub struct BusyTimerGuard { busy_timer: Arc>, } @@ -293,11 +306,11 @@ pub(crate) struct BusyTimer { } impl BusyTimer { - pub(crate) fn new() -> Self { + fn new() -> Self { BusyTimer::default() } - pub(crate) fn increment_active_requests(&mut self) { + fn increment_active_requests(&mut self) { if self.active_requests == 0 { if let Some(start) = self.start.take() { self.busy_ns += start.elapsed(); @@ -308,7 +321,7 @@ impl BusyTimer { self.active_requests += 1; } - pub(crate) fn decrement_active_requests(&mut self) { + fn decrement_active_requests(&mut self) { self.active_requests -= 1; if self.active_requests == 0 { @@ -316,7 +329,7 @@ impl BusyTimer { } } - pub(crate) fn current(&mut self) -> Duration { + fn current(&mut self) -> Duration { if let Some(start) = self.start { self.busy_ns + start.elapsed() } else { diff --git a/apollo-router/src/orbiter/mod.rs b/apollo-router/src/orbiter/mod.rs index b3f7ddcf04..8e47323157 100644 --- a/apollo-router/src/orbiter/mod.rs +++ b/apollo-router/src/orbiter/mod.rs @@ -1,4 +1,6 @@ use std::collections::HashMap; +use std::collections::HashSet; +use std::str::FromStr; use std::sync::Arc; use std::time::Duration; @@ -6,9 +8,7 @@ use async_trait::async_trait; use clap::CommandFactory; use http::header::CONTENT_TYPE; use http::header::USER_AGENT; -use jsonschema::output::BasicOutput; -use jsonschema::paths::PathChunk; -use jsonschema::JSONSchema; +use jsonpath_rust::JsonPathInst; use mime::APPLICATION_JSON; use once_cell::sync::OnceCell; use serde::Serialize; @@ -235,77 +235,71 @@ fn visit_config(usage: &mut HashMap, config: &Value) { // We have to be careful not to expose names of headers, metadata or anything else sensitive. let raw_json_schema = serde_json::to_value(generate_config_schema()).expect("config schema must be valid"); - let compiled_json_schema = JSONSchema::compile( - &serde_json::to_value(&raw_json_schema).expect("config schema must be valid"), - ) - .expect("config schema must compile"); - - // We can use jsonschema to give us annotations about the validated config. This means that we get - // a pointer into the config document and also a pointer into the schema. - // For this to work ALL config must have an annotation, e.g. documentation. - // For each config path we need to sanitize the it for arrays and also custom names e.g. header names. - // This corresponds to the json schema keywords of `items` and `additionalProperties` - if let BasicOutput::Valid(output) = compiled_json_schema.apply(config).basic() { - for item in output { - let instance_ptr = item.instance_location(); - let value = config - .pointer(&instance_ptr.to_string()) - .expect("pointer must point to value"); - - // Compose the redacted path. - let mut path = Vec::new(); - for chunk in item.keyword_location() { - if let PathChunk::Property(property) = chunk { - // We hit a properties keyword, we can grab the next keyword as it'll be a property name. - path.push(property.to_string()); - } - if &PathChunk::Keyword("additionalProperties") == chunk { - // This is free format properties. It's redacted - path.push("".to_string()); - } - } + // We can't use json schema to redact the config as we don't have the annotations. + // Instead, we get the set of properties from the schema and anything that doesn't match a property is redacted. + let path = JsonPathInst::from_str("$..properties").expect("properties path must be valid"); + let slice = path.find_slice(&raw_json_schema); + let schema_properties: HashSet = slice + .iter() + .filter_map(|v| v.as_object()) + .flat_map(|o| o.keys()) + .map(|s| s.to_string()) + .collect(); + + // Now for each leaf in the config we get the path and redact anything that isn't in the schema. + visit_value(&schema_properties, usage, config, ""); +} - let path = path.join("."); - if matches!(item.keyword_location().last(), Some(&PathChunk::Index(_))) { - *usage - .entry(format!("configuration.{path}.len")) - .or_default() += 1; - } - match value { - Value::Bool(value) => { - *usage - .entry(format!("configuration.{path}.{value}")) - .or_default() += 1; - } - Value::Number(value) => { - *usage - .entry(format!("configuration.{path}.{value}")) - .or_default() += 1; - } - Value::String(_) => { - // Strings are never output +fn visit_value( + schema_properties: &HashSet, + usage: &mut HashMap, + value: &Value, + path: &str, +) { + match value { + Value::Bool(value) => { + *usage + .entry(format!("configuration.{path}.{value}")) + .or_default() += 1; + } + Value::Number(value) => { + *usage + .entry(format!("configuration.{path}.{value}")) + .or_default() += 1; + } + Value::String(_) => { + // Strings are never output + *usage + .entry(format!("configuration.{path}.")) + .or_default() += 1; + } + Value::Object(o) => { + for (key, value) in o { + let key = if schema_properties.contains(key) { + key + } else { + "" + }; + + if path.is_empty() { + visit_value(schema_properties, usage, value, key); + } else { + visit_value(schema_properties, usage, value, &format!("{path}.{key}")); *usage - .entry(format!("configuration.{path}.")) + .entry(format!("configuration.{path}.{key}.len")) .or_default() += 1; } - Value::Object(o) => { - if matches!( - item.keyword_location().last(), - Some(&PathChunk::Property(_)) - ) { - let schema_node = raw_json_schema - .pointer(&item.keyword_location().to_string()) - .expect("schema node must resolve"); - if let Some(Value::Bool(true)) = schema_node.get("additionalProperties") { - *usage - .entry(format!("configuration.{path}.len")) - .or_default() += o.len() as u64; - } - } - } - _ => {} } } + Value::Array(a) => { + for value in a { + visit_value(schema_properties, usage, value, path); + } + *usage + .entry(format!("configuration.{path}.array.len")) + .or_default() += a.len() as u64; + } + Value::Null => {} } } @@ -343,6 +337,10 @@ mod test { }); } + // The following two tests are ignored because since allowing refs in schema we can no longer + // examine the annotations for redaction. + // https://github.com/Stranger6667/jsonschema-rs/issues/403 + // We should remove the orbiter code and move to otel for both anonymous and non-anonymous telemetry. #[test] fn test_visit_config() { let config = Configuration::from_str(include_str!("testdata/redaction.router.yaml")) diff --git a/apollo-router/src/orbiter/snapshots/apollo_router__orbiter__test__create_report.snap b/apollo-router/src/orbiter/snapshots/apollo_router__orbiter__test__create_report.snap index 7d2a0bc2ff..98ee3ee859 100644 --- a/apollo-router/src/orbiter/snapshots/apollo_router__orbiter__test__create_report.snap +++ b/apollo-router/src/orbiter/snapshots/apollo_router__orbiter__test__create_report.snap @@ -8,21 +8,47 @@ platform: os: "[os]" continuous_integration: "[ci]" usage: + configuration.headers.all.len: 1 + configuration.headers.all.request.array.len: 3 configuration.headers.all.request.insert.len: 1 configuration.headers.all.request.insert.name.: 1 + configuration.headers.all.request.insert.name.len: 1 configuration.headers.all.request.insert.value.: 1 + configuration.headers.all.request.insert.value.len: 1 + configuration.headers.all.request.len: 1 configuration.headers.all.request.propagate.len: 2 configuration.headers.all.request.propagate.named.: 2 + configuration.headers.all.request.propagate.named.len: 2 + configuration.headers.subgraphs..len: 1 + configuration.headers.subgraphs..request.array.len: 2 configuration.headers.subgraphs..request.insert.len: 1 configuration.headers.subgraphs..request.insert.name.: 1 + configuration.headers.subgraphs..request.insert.name.len: 1 configuration.headers.subgraphs..request.insert.value.: 1 + configuration.headers.subgraphs..request.insert.value.len: 1 + configuration.headers.subgraphs..request.len: 1 configuration.headers.subgraphs..request.remove.len: 1 configuration.headers.subgraphs..request.remove.named.: 1 - configuration.override_subgraph_url.len: 1 + configuration.headers.subgraphs..request.remove.named.len: 1 + configuration.headers.subgraphs.len: 1 + configuration.override_subgraph_url..: 1 + configuration.override_subgraph_url..len: 1 configuration.plugins.len: 0 + configuration.telemetry.apollo.len: 1 + configuration.telemetry.apollo.send_headers.except.: 2 + configuration.telemetry.apollo.send_headers.except.array.len: 2 + configuration.telemetry.apollo.send_headers.except.len: 1 configuration.telemetry.apollo.send_headers.len: 1 + configuration.telemetry.exporters.len: 1 + configuration.telemetry.exporters.tracing.len: 1 + configuration.telemetry.exporters.tracing.otlp.enabled.len: 1 configuration.telemetry.exporters.tracing.otlp.enabled.true: 1 configuration.telemetry.exporters.tracing.otlp.endpoint.: 1 - configuration.telemetry.exporters.tracing.otlp.grpc.metadata.len: 2 + configuration.telemetry.exporters.tracing.otlp.endpoint.len: 1 + configuration.telemetry.exporters.tracing.otlp.grpc.len: 1 + configuration.telemetry.exporters.tracing.otlp.grpc.metadata..: 2 + configuration.telemetry.exporters.tracing.otlp.grpc.metadata..len: 2 + configuration.telemetry.exporters.tracing.otlp.grpc.metadata.len: 1 + configuration.telemetry.exporters.tracing.otlp.len: 1 configuration.telemetry.exporters.tracing.otlp.protocol.: 1 - + configuration.telemetry.exporters.tracing.otlp.protocol.len: 1 diff --git a/apollo-router/src/orbiter/snapshots/apollo_router__orbiter__test__create_report_invalid_validated_yaml.snap b/apollo-router/src/orbiter/snapshots/apollo_router__orbiter__test__create_report_invalid_validated_yaml.snap index c00efc1ec5..6466f89eb0 100644 --- a/apollo-router/src/orbiter/snapshots/apollo_router__orbiter__test__create_report_invalid_validated_yaml.snap +++ b/apollo-router/src/orbiter/snapshots/apollo_router__orbiter__test__create_report_invalid_validated_yaml.snap @@ -8,5 +8,5 @@ platform: os: "[os]" continuous_integration: "[ci]" usage: + configuration..: 1 configuration.plugins.len: 0 - diff --git a/apollo-router/src/orbiter/snapshots/apollo_router__orbiter__test__visit_config.snap b/apollo-router/src/orbiter/snapshots/apollo_router__orbiter__test__visit_config.snap index 643e174fd6..ec1d84c634 100644 --- a/apollo-router/src/orbiter/snapshots/apollo_router__orbiter__test__visit_config.snap +++ b/apollo-router/src/orbiter/snapshots/apollo_router__orbiter__test__visit_config.snap @@ -2,20 +2,46 @@ source: apollo-router/src/orbiter/mod.rs expression: usage --- +configuration.headers.all.len: 1 +configuration.headers.all.request.array.len: 3 configuration.headers.all.request.insert.len: 1 configuration.headers.all.request.insert.name.: 1 +configuration.headers.all.request.insert.name.len: 1 configuration.headers.all.request.insert.value.: 1 +configuration.headers.all.request.insert.value.len: 1 +configuration.headers.all.request.len: 1 configuration.headers.all.request.propagate.len: 2 configuration.headers.all.request.propagate.named.: 2 +configuration.headers.all.request.propagate.named.len: 2 +configuration.headers.subgraphs..len: 1 +configuration.headers.subgraphs..request.array.len: 2 configuration.headers.subgraphs..request.insert.len: 1 configuration.headers.subgraphs..request.insert.name.: 1 +configuration.headers.subgraphs..request.insert.name.len: 1 configuration.headers.subgraphs..request.insert.value.: 1 +configuration.headers.subgraphs..request.insert.value.len: 1 +configuration.headers.subgraphs..request.len: 1 configuration.headers.subgraphs..request.remove.len: 1 configuration.headers.subgraphs..request.remove.named.: 1 -configuration.override_subgraph_url.len: 1 +configuration.headers.subgraphs..request.remove.named.len: 1 +configuration.headers.subgraphs.len: 1 +configuration.override_subgraph_url..: 1 +configuration.override_subgraph_url..len: 1 +configuration.telemetry.apollo.len: 1 +configuration.telemetry.apollo.send_headers.except.: 2 +configuration.telemetry.apollo.send_headers.except.array.len: 2 +configuration.telemetry.apollo.send_headers.except.len: 1 configuration.telemetry.apollo.send_headers.len: 1 +configuration.telemetry.exporters.len: 1 +configuration.telemetry.exporters.tracing.len: 1 +configuration.telemetry.exporters.tracing.otlp.enabled.len: 1 configuration.telemetry.exporters.tracing.otlp.enabled.true: 1 configuration.telemetry.exporters.tracing.otlp.endpoint.: 1 -configuration.telemetry.exporters.tracing.otlp.grpc.metadata.len: 2 +configuration.telemetry.exporters.tracing.otlp.endpoint.len: 1 +configuration.telemetry.exporters.tracing.otlp.grpc.len: 1 +configuration.telemetry.exporters.tracing.otlp.grpc.metadata..: 2 +configuration.telemetry.exporters.tracing.otlp.grpc.metadata..len: 2 +configuration.telemetry.exporters.tracing.otlp.grpc.metadata.len: 1 +configuration.telemetry.exporters.tracing.otlp.len: 1 configuration.telemetry.exporters.tracing.otlp.protocol.: 1 - +configuration.telemetry.exporters.tracing.otlp.protocol.len: 1 diff --git a/apollo-router/src/orbiter/snapshots/apollo_router__orbiter__test__visit_config_that_needed_upgrade.snap b/apollo-router/src/orbiter/snapshots/apollo_router__orbiter__test__visit_config_that_needed_upgrade.snap index 3122354fc5..f2085a8673 100644 --- a/apollo-router/src/orbiter/snapshots/apollo_router__orbiter__test__visit_config_that_needed_upgrade.snap +++ b/apollo-router/src/orbiter/snapshots/apollo_router__orbiter__test__visit_config_that_needed_upgrade.snap @@ -2,5 +2,5 @@ source: apollo-router/src/orbiter/mod.rs expression: usage --- +configuration.supergraph.defer_support.len: 1 configuration.supergraph.defer_support.true: 1 - diff --git a/apollo-router/src/plugins/authentication/mod.rs b/apollo-router/src/plugins/authentication/mod.rs index 19097212e2..32c210c5c3 100644 --- a/apollo-router/src/plugins/authentication/mod.rs +++ b/apollo-router/src/plugins/authentication/mod.rs @@ -3,6 +3,7 @@ use std::collections::HashMap; use std::ops::ControlFlow; use std::str::FromStr; +use std::sync::Arc; use std::time::Duration; use std::time::SystemTime; use std::time::UNIX_EPOCH; @@ -412,21 +413,23 @@ impl Plugin for AuthenticationPlugin { async fn new(init: PluginInit) -> Result { let subgraph = if let Some(config) = init.config.subgraph { let all = if let Some(config) = &config.all { - Some(subgraph::make_signing_params(config, "all").await?) + Some(Arc::new( + subgraph::make_signing_params(config, "all").await?, + )) } else { None }; - let mut subgraphs: HashMap = Default::default(); + let mut subgraphs: HashMap> = Default::default(); for (subgraph_name, config) in &config.subgraphs { subgraphs.insert( subgraph_name.clone(), - subgraph::make_signing_params(config, subgraph_name.as_str()).await?, + Arc::new(subgraph::make_signing_params(config, subgraph_name.as_str()).await?), ); } Some(SubgraphAuth { - signing_params: { SigningParams { all, subgraphs } }, + signing_params: Arc::new(SigningParams { all, subgraphs }), }) } else { None diff --git a/apollo-router/src/plugins/authentication/subgraph.rs b/apollo-router/src/plugins/authentication/subgraph.rs index 68ece88fce..40006c2fb0 100644 --- a/apollo-router/src/plugins/authentication/subgraph.rs +++ b/apollo-router/src/plugins/authentication/subgraph.rs @@ -185,8 +185,8 @@ pub(crate) struct Config { #[allow(dead_code)] #[derive(Clone, Default)] pub(crate) struct SigningParams { - pub(crate) all: Option, - pub(crate) subgraphs: HashMap, + pub(crate) all: Option>, + pub(crate) subgraphs: HashMap>, } #[derive(Clone)] @@ -199,7 +199,7 @@ pub(crate) struct SigningParamsConfig { impl SigningParamsConfig { pub(crate) async fn sign( - self, + &self, mut req: Request, subgraph_name: &str, ) -> Result, BoxError> { @@ -236,9 +236,10 @@ impl SigningParamsConfig { increment_success_counter(subgraph_name); Ok(req) } + // This function is the same as above, except it's a new one because () doesn't implement HttpBody` pub(crate) async fn sign_empty( - self, + &self, mut req: Request<()>, subgraph_name: &str, ) -> Result, BoxError> { @@ -360,7 +361,7 @@ fn get_signing_settings(signing_params: &SigningParamsConfig) -> SigningSettings } pub(super) struct SubgraphAuth { - pub(super) signing_params: SigningParams, + pub(super) signing_params: Arc, } impl SubgraphAuth { @@ -385,7 +386,7 @@ impl SubgraphAuth { } impl SubgraphAuth { - fn params_for_service(&self, service_name: &str) -> Option { + fn params_for_service(&self, service_name: &str) -> Option> { self.signing_params .subgraphs .get(service_name) @@ -527,7 +528,7 @@ mod test { .returning(example_response); let mut service = SubgraphAuth { - signing_params: SigningParams { + signing_params: Arc::new(SigningParams { all: make_signing_params( &AuthConfig::AWSSigV4(AWSSigV4Config::Hardcoded(AWSSigV4HardcodedConfig { access_key_id: "id".to_string(), @@ -539,9 +540,10 @@ mod test { "all", ) .await - .ok(), + .ok() + .map(Arc::new), subgraphs: Default::default(), - }, + }), } .subgraph_service("test_subgraph", mock.boxed()); @@ -579,7 +581,7 @@ mod test { .returning(example_response); let mut service = SubgraphAuth { - signing_params: SigningParams { + signing_params: Arc::new(SigningParams { all: make_signing_params( &AuthConfig::AWSSigV4(AWSSigV4Config::Hardcoded(AWSSigV4HardcodedConfig { access_key_id: "id".to_string(), @@ -591,9 +593,10 @@ mod test { "all", ) .await - .ok(), + .ok() + .map(Arc::new), subgraphs: Default::default(), - }, + }), } .subgraph_service("test_subgraph", mock.boxed()); @@ -643,7 +646,7 @@ mod test { ) -> hyper::Request { let signing_params = { let ctx = request.context.extensions().lock(); - let sp = ctx.get::(); + let sp = ctx.get::>(); sp.cloned().unwrap() }; diff --git a/apollo-router/src/plugins/authorization/authenticated.rs b/apollo-router/src/plugins/authorization/authenticated.rs index 23c2ab6bda..bca06237c6 100644 --- a/apollo-router/src/plugins/authorization/authenticated.rs +++ b/apollo-router/src/plugins/authorization/authenticated.rs @@ -645,7 +645,7 @@ mod tests { let (doc, paths) = filter(BASIC_SCHEMA, QUERY); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, result: doc, paths @@ -668,7 +668,7 @@ mod tests { let (doc, paths) = filter(BASIC_SCHEMA, QUERY); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, result: doc, paths @@ -691,7 +691,7 @@ mod tests { let (doc, paths) = filter(BASIC_SCHEMA, QUERY); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, result: doc, paths @@ -711,7 +711,7 @@ mod tests { let (doc, paths) = filter(BASIC_SCHEMA, QUERY); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, result: doc, paths @@ -736,7 +736,7 @@ mod tests { let (doc, paths) = filter(BASIC_SCHEMA, QUERY); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, result: doc, paths @@ -761,7 +761,7 @@ mod tests { let (doc, paths) = filter(BASIC_SCHEMA, QUERY); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, result: doc, paths @@ -788,7 +788,7 @@ mod tests { let (doc, paths) = filter(BASIC_SCHEMA, QUERY); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, result: doc, paths @@ -814,7 +814,7 @@ mod tests { let (doc, paths) = filter(BASIC_SCHEMA, QUERY); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, result: doc, paths @@ -837,7 +837,7 @@ mod tests { let (doc, paths) = filter(BASIC_SCHEMA, QUERY); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, result: doc, paths @@ -863,7 +863,7 @@ mod tests { let (doc, paths) = filter(BASIC_SCHEMA, QUERY); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, result: doc, paths @@ -927,7 +927,7 @@ mod tests { let (doc, paths) = filter(INTERFACE_SCHEMA, QUERY); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, result: doc, paths @@ -950,7 +950,7 @@ mod tests { let (doc, paths) = filter(INTERFACE_SCHEMA, QUERY2); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY2, result: doc, paths @@ -1018,7 +1018,7 @@ mod tests { let (doc, paths) = filter(INTERFACE_FIELD_SCHEMA, QUERY); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, result: doc, paths @@ -1043,7 +1043,7 @@ mod tests { let (doc, paths) = filter(INTERFACE_FIELD_SCHEMA, QUERY2); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY2, result: doc, paths @@ -1108,7 +1108,7 @@ mod tests { let (doc, paths) = filter(UNION_MEMBERS_SCHEMA, QUERY); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, result: doc, paths @@ -1196,7 +1196,7 @@ mod tests { let (doc, paths) = filter(RENAMED_SCHEMA, QUERY); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, result: doc, paths @@ -1357,7 +1357,7 @@ mod tests { let (doc, paths) = filter(SCHEMA, QUERY); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, result: doc, paths @@ -1377,7 +1377,7 @@ mod tests { let (doc, paths) = filter(SCHEMA, QUERY2); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY2, result: doc, paths diff --git a/apollo-router/src/plugins/authorization/policy.rs b/apollo-router/src/plugins/authorization/policy.rs index d46cb4943a..18d68ce5aa 100644 --- a/apollo-router/src/plugins/authorization/policy.rs +++ b/apollo-router/src/plugins/authorization/policy.rs @@ -792,7 +792,7 @@ mod tests { let extracted_policies = extract(BASIC_SCHEMA, QUERY); let (doc, paths) = filter(BASIC_SCHEMA, QUERY, HashSet::new()); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, extracted_policies: &extracted_policies, successful_policies: Vec::new(), @@ -807,7 +807,7 @@ mod tests { .into_iter() .collect(), ); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, extracted_policies: &extracted_policies, successful_policies: ["profile".to_string(), "internal".to_string()] @@ -828,7 +828,7 @@ mod tests { .into_iter() .collect(), ); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, extracted_policies: &extracted_policies, successful_policies: [ @@ -853,7 +853,7 @@ mod tests { .into_iter() .collect(), ); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, extracted_policies: &extracted_policies, successful_policies: [ @@ -882,7 +882,7 @@ mod tests { let extracted_policies = extract(BASIC_SCHEMA, QUERY); let (doc, paths) = filter(BASIC_SCHEMA, QUERY, HashSet::new()); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, extracted_policies: &extracted_policies, successful_policies: Vec::new(), @@ -908,7 +908,7 @@ mod tests { let extracted_policies = extract(BASIC_SCHEMA, QUERY); let (doc, paths) = filter(BASIC_SCHEMA, QUERY, HashSet::new()); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, extracted_policies: &extracted_policies, successful_policies: Vec::new(), @@ -933,7 +933,7 @@ mod tests { let extracted_policies = extract(BASIC_SCHEMA, QUERY); let (doc, paths) = filter(BASIC_SCHEMA, QUERY, HashSet::new()); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, extracted_policies: &extracted_policies, successful_policies: Vec::new(), @@ -955,7 +955,7 @@ mod tests { let extracted_policies = extract(BASIC_SCHEMA, QUERY); let (doc, paths) = filter(BASIC_SCHEMA, QUERY, HashSet::new()); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, extracted_policies: &extracted_policies, successful_policies: Vec::new(), @@ -982,7 +982,7 @@ mod tests { let extracted_policies = extract(BASIC_SCHEMA, QUERY); let (doc, paths) = filter(BASIC_SCHEMA, QUERY, HashSet::new()); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, extracted_policies: &extracted_policies, successful_policies: Vec::new(), @@ -1009,7 +1009,7 @@ mod tests { let extracted_policies = extract(BASIC_SCHEMA, QUERY); let (doc, paths) = filter(BASIC_SCHEMA, QUERY, HashSet::new()); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, extracted_policies: &extracted_policies, successful_policies: Vec::new(), @@ -1038,7 +1038,7 @@ mod tests { let extracted_policies = extract(BASIC_SCHEMA, QUERY); let (doc, paths) = filter(BASIC_SCHEMA, QUERY, HashSet::new()); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, extracted_policies: &extracted_policies, successful_policies: Vec::new(), @@ -1053,7 +1053,7 @@ mod tests { .into_iter() .collect(), ); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, extracted_policies: &extracted_policies, successful_policies: ["read user".to_string(), "read username".to_string()] @@ -1084,7 +1084,7 @@ mod tests { let extracted_policies = extract(BASIC_SCHEMA, QUERY); let (doc, paths) = filter(BASIC_SCHEMA, QUERY, HashSet::new()); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, extracted_policies: &extracted_policies, successful_policies: Vec::new(), @@ -1105,7 +1105,7 @@ mod tests { let extracted_policies = extract(BASIC_SCHEMA, QUERY); let (doc, paths) = filter(BASIC_SCHEMA, QUERY, HashSet::new()); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, extracted_policies: &extracted_policies, successful_policies: Vec::new(), @@ -1120,7 +1120,7 @@ mod tests { .into_iter() .collect(), ); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, extracted_policies: &extracted_policies, successful_policies: ["read user".to_string(), "internal".to_string()] @@ -1135,7 +1135,7 @@ mod tests { QUERY, ["read user".to_string()].into_iter().collect(), ); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, extracted_policies: &extracted_policies, successful_policies: ["read user".to_string(),].into_iter().collect(), @@ -1150,7 +1150,7 @@ mod tests { .into_iter() .collect(), ); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, extracted_policies: &extracted_policies, successful_policies: ["admin".to_string(), "read user".to_string()] @@ -1215,7 +1215,7 @@ mod tests { let extracted_policies = extract(INTERFACE_SCHEMA, QUERY); let (doc, paths) = filter(INTERFACE_SCHEMA, QUERY, HashSet::new()); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, extracted_policies: &extracted_policies, successful_policies: Vec::new(), @@ -1228,7 +1228,7 @@ mod tests { QUERY, ["itf".to_string()].into_iter().collect(), ); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, extracted_policies: &extracted_policies, successful_policies: ["itf".to_string()].into_iter().collect(), @@ -1252,7 +1252,7 @@ mod tests { let extracted_policies = extract(INTERFACE_SCHEMA, QUERY2); let (doc, paths) = filter(INTERFACE_SCHEMA, QUERY2, HashSet::new()); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY2, extracted_policies: &extracted_policies, successful_policies: Vec::new(), @@ -1265,7 +1265,7 @@ mod tests { QUERY2, ["itf".to_string()].into_iter().collect(), ); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY2, extracted_policies: &extracted_policies, successful_policies: ["itf".to_string()].into_iter().collect(), @@ -1278,7 +1278,7 @@ mod tests { QUERY2, ["itf".to_string(), "a".to_string()].into_iter().collect(), ); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY2, extracted_policies: &extracted_policies, successful_policies: ["itf".to_string(), "a".to_string()].into_iter().collect(), @@ -1345,7 +1345,7 @@ mod tests { let extracted_policies = extract(INTERFACE_FIELD_SCHEMA, QUERY); let (doc, paths) = filter(INTERFACE_FIELD_SCHEMA, QUERY, HashSet::new()); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, extracted_policies: &extracted_policies, successful_policies: Vec::new(), @@ -1371,7 +1371,7 @@ mod tests { let extracted_policies = extract(INTERFACE_FIELD_SCHEMA, QUERY2); let (doc, paths) = filter(INTERFACE_FIELD_SCHEMA, QUERY2, HashSet::new()); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY2, extracted_policies: &extracted_policies, successful_policies: Vec::new(), @@ -1439,7 +1439,7 @@ mod tests { QUERY, ["a".to_string()].into_iter().collect(), ); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, extracted_policies: &extracted_policies, successful_policies: ["a".to_string()].into_iter().collect(), @@ -1519,7 +1519,7 @@ mod tests { let extracted_policies = extract(RENAMED_SCHEMA, QUERY); let (doc, paths) = filter(RENAMED_SCHEMA, QUERY, HashSet::new()); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, extracted_policies: &extracted_policies, successful_policies: Vec::new(), @@ -1599,7 +1599,7 @@ mod tests { let (doc, paths) = filter(SCHEMA, QUERY, HashSet::new()); - insta::assert_display_snapshot!(doc); + insta::assert_snapshot!(doc); insta::assert_debug_snapshot!(paths); static QUERY2: &str = r#" @@ -1616,7 +1616,7 @@ mod tests { let (doc, paths) = filter(SCHEMA, QUERY2, HashSet::new()); - insta::assert_display_snapshot!(doc); + insta::assert_snapshot!(doc); insta::assert_debug_snapshot!(paths); } } diff --git a/apollo-router/src/plugins/authorization/scopes.rs b/apollo-router/src/plugins/authorization/scopes.rs index 3298a3dcf1..d83988e220 100644 --- a/apollo-router/src/plugins/authorization/scopes.rs +++ b/apollo-router/src/plugins/authorization/scopes.rs @@ -796,7 +796,7 @@ mod tests { let extracted_scopes = extract(BASIC_SCHEMA, QUERY); let (doc, paths) = filter(BASIC_SCHEMA, QUERY, HashSet::new()); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, extracted_scopes: &extracted_scopes, scopes: Vec::new(), @@ -812,7 +812,7 @@ mod tests { .collect(), ); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, extracted_scopes: &extracted_scopes, scopes: ["profile".to_string(), "internal".to_string()] @@ -834,7 +834,7 @@ mod tests { .into_iter() .collect(), ); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, extracted_scopes: &extracted_scopes, scopes: [ @@ -860,7 +860,7 @@ mod tests { .into_iter() .collect(), ); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, extracted_scopes: &extracted_scopes, scopes: [ @@ -890,7 +890,7 @@ mod tests { let (doc, paths) = filter(BASIC_SCHEMA, QUERY, HashSet::new()); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, extracted_scopes: &extracted_scopes, scopes: Vec::new(), @@ -917,7 +917,7 @@ mod tests { let (doc, paths) = filter(BASIC_SCHEMA, QUERY, HashSet::new()); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, extracted_scopes: &extracted_scopes, scopes: Vec::new(), @@ -944,7 +944,7 @@ mod tests { let (doc, paths) = filter(BASIC_SCHEMA, QUERY, HashSet::new()); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, extracted_scopes: &extracted_scopes, scopes: Vec::new(), @@ -968,7 +968,7 @@ mod tests { let (doc, paths) = filter(BASIC_SCHEMA, QUERY, HashSet::new()); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, extracted_scopes: &extracted_scopes, scopes: Vec::new(), @@ -997,7 +997,7 @@ mod tests { let (doc, paths) = filter(BASIC_SCHEMA, QUERY, HashSet::new()); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, extracted_scopes: &extracted_scopes, scopes: Vec::new(), @@ -1027,7 +1027,7 @@ mod tests { let (doc, paths) = filter(BASIC_SCHEMA, QUERY, HashSet::new()); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, extracted_scopes: &extracted_scopes, scopes: Vec::new(), @@ -1041,7 +1041,7 @@ mod tests { ["read:user".to_string()].into_iter().collect(), ); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, extracted_scopes: &extracted_scopes, scopes: ["read:user".to_string()].into_iter().collect(), @@ -1057,7 +1057,7 @@ mod tests { .collect(), ); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, extracted_scopes: &extracted_scopes, scopes: ["read:user".to_string(), "read:username".to_string()] @@ -1091,7 +1091,7 @@ mod tests { let (doc, paths) = filter(BASIC_SCHEMA, QUERY, HashSet::new()); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, extracted_scopes: &extracted_scopes, scopes: Vec::new(), @@ -1105,7 +1105,7 @@ mod tests { ["read:user".to_string()].into_iter().collect(), ); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, extracted_scopes: &extracted_scopes, scopes: ["read:user".to_string()].into_iter().collect(), @@ -1121,7 +1121,7 @@ mod tests { .collect(), ); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, extracted_scopes: &extracted_scopes, scopes: ["read:user".to_string(), "read:username".to_string()] @@ -1152,7 +1152,7 @@ mod tests { let extracted_scopes = extract(BASIC_SCHEMA, QUERY); let (doc, paths) = filter(BASIC_SCHEMA, QUERY, HashSet::new()); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, extracted_scopes: &extracted_scopes, scopes: Vec::new(), @@ -1215,7 +1215,7 @@ mod tests { let extracted_scopes = extract(INTERFACE_SCHEMA, QUERY); let (doc, paths) = filter(INTERFACE_SCHEMA, QUERY, HashSet::new()); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, extracted_scopes: &extracted_scopes, scopes: Vec::new(), @@ -1229,7 +1229,7 @@ mod tests { ["itf".to_string()].into_iter().collect(), ); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, extracted_scopes: &extracted_scopes, scopes: ["itf".to_string()].into_iter().collect(), @@ -1254,7 +1254,7 @@ mod tests { let extracted_scopes = extract(INTERFACE_SCHEMA, QUERY2); let (doc, paths) = filter(INTERFACE_SCHEMA, QUERY2, HashSet::new()); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY2, extracted_scopes: &extracted_scopes, scopes: Vec::new(), @@ -1268,7 +1268,7 @@ mod tests { ["itf".to_string()].into_iter().collect(), ); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY2, extracted_scopes: &extracted_scopes, scopes: ["itf".to_string()].into_iter().collect(), @@ -1284,7 +1284,7 @@ mod tests { .collect(), ); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY2, extracted_scopes: &extracted_scopes, scopes: ["itf".to_string(), "a".to_string(), "b".to_string()] @@ -1354,7 +1354,7 @@ mod tests { let (doc, paths) = filter(INTERFACE_FIELD_SCHEMA, QUERY, HashSet::new()); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, extracted_scopes: &extracted_scopes, scopes: Vec::new(), @@ -1382,7 +1382,7 @@ mod tests { let (doc, paths) = filter(INTERFACE_FIELD_SCHEMA, QUERY2, HashSet::new()); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY2, extracted_scopes: &extracted_scopes, scopes: Vec::new(), @@ -1452,7 +1452,7 @@ mod tests { ["a".to_string(), "b".to_string()].into_iter().collect(), ); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, extracted_scopes: &extracted_scopes, scopes: ["a".to_string(), "b".to_string()].into_iter().collect(), @@ -1541,7 +1541,7 @@ mod tests { let (doc, paths) = filter(RENAMED_SCHEMA, QUERY, HashSet::new()); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, extracted_scopes: &extracted_scopes, scopes: Vec::new(), @@ -1622,7 +1622,7 @@ mod tests { let (doc, paths) = filter(SCHEMA, QUERY, HashSet::new()); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY, extracted_scopes: &extracted_scopes, scopes: Vec::new(), @@ -1646,7 +1646,7 @@ mod tests { let (doc, paths) = filter(SCHEMA, QUERY2, HashSet::new()); - insta::assert_display_snapshot!(TestResult { + insta::assert_snapshot!(TestResult { query: QUERY2, extracted_scopes: &extracted_scopes, scopes: Vec::new(), diff --git a/apollo-router/src/plugins/demand_control/cost_calculator/static_cost.rs b/apollo-router/src/plugins/demand_control/cost_calculator/static_cost.rs index 80ef8de562..6b713d19bf 100644 --- a/apollo-router/src/plugins/demand_control/cost_calculator/static_cost.rs +++ b/apollo-router/src/plugins/demand_control/cost_calculator/static_cost.rs @@ -416,7 +416,7 @@ mod tests { let config: Arc = Arc::new(Default::default()); let (_schema, query) = parse_schema_and_operation(schema_str, query_str, &config); - let mut planner = BridgeQueryPlanner::new(schema_str.to_string(), config.clone()) + let mut planner = BridgeQueryPlanner::new(schema_str.to_string(), config.clone(), None) .await .unwrap(); diff --git a/apollo-router/src/plugins/demand_control/mod.rs b/apollo-router/src/plugins/demand_control/mod.rs index e4a8d1f692..0087e193cc 100644 --- a/apollo-router/src/plugins/demand_control/mod.rs +++ b/apollo-router/src/plugins/demand_control/mod.rs @@ -33,11 +33,13 @@ use crate::register_plugin; use crate::services::execution; use crate::services::execution::BoxService; use crate::services::subgraph; +use crate::Context; pub(crate) mod cost_calculator; pub(crate) mod strategy; /// The cost calculation information stored in context for use in telemetry and other plugins that need to know what cost was calculated. +#[derive(Debug, Clone)] pub(crate) struct CostContext { pub(crate) estimated: f64, pub(crate) actual: f64, @@ -209,6 +211,20 @@ pub(crate) struct DemandControl { strategy_factory: StrategyFactory, } +impl DemandControl { + fn report_operation_metric(context: Context) { + let guard = context.extensions().lock(); + let cost_context = guard.get::(); + let result = cost_context.map_or("NO_CONTEXT", |c| c.result); + u64_counter!( + "apollo.router.operations.demand_control", + "Total operations with demand control enabled", + 1, + "demand_control.result" = result + ); + } +} + #[async_trait::async_trait] impl Plugin for DemandControl { type Config = DemandControlConfig; @@ -260,6 +276,15 @@ impl Plugin for DemandControl { .expect("must have strategy") .clone(); let context = resp.context.clone(); + + // We want to sequence this code to run after all the subgraph responses have been scored. + // To do so without collecting all the results, we chain this "empty" stream onto the end. + let report_operation_metric = + futures::stream::unfold(resp.context.clone(), |ctx| async move { + Self::report_operation_metric(ctx); + None + }); + resp.response = resp.response.map(move |resp| { // Here we are going to abort the stream if the cost is too high // First we map based on cost, then we use take while to abort the stream if an error is emitted. @@ -267,24 +292,28 @@ impl Plugin for DemandControl { resp.flat_map(move |resp| { match strategy.on_execution_response(&context, req.as_ref(), &resp) { Ok(_) => Either::Left(stream::once(future::ready(Ok(resp)))), - Err(err) => Either::Right(stream::iter(vec![ - // This is the error we are returning to the user - Ok(graphql::Response::builder() - .errors( - err.into_graphql_errors() - .expect("must be able to convert to graphql error"), - ) - .extensions(crate::json_ext::Object::new()) - .build()), - // This will terminate the stream - Err(()), - ])), + Err(err) => { + Either::Right(stream::iter(vec![ + // This is the error we are returning to the user + Ok(graphql::Response::builder() + .errors( + err.into_graphql_errors().expect( + "must be able to convert to graphql error", + ), + ) + .extensions(crate::json_ext::Object::new()) + .build()), + // This will terminate the stream + Err(()), + ])) + } } }) // Terminate the stream on error .take_while(|resp| future::ready(resp.is_ok())) // Unwrap the result. This is safe because we are terminating the stream on error. .map(|i| i.expect("error used to terminate stream")) + .chain(report_operation_metric) .boxed() }); resp @@ -377,6 +406,7 @@ mod test { use crate::graphql; use crate::graphql::Response; + use crate::metrics::FutureMetricsExt; use crate::plugins::demand_control::DemandControl; use crate::plugins::demand_control::DemandControlError; use crate::plugins::test::PluginTestHarness; @@ -459,6 +489,48 @@ mod test { insta::assert_yaml_snapshot!(body); } + #[tokio::test] + async fn test_operation_metrics() { + async { + test_on_execution(include_str!( + "fixtures/measure_on_execution_request.router.yaml" + )) + .await; + assert_counter!( + "apollo.router.operations.demand_control", + 1, + "demand_control.result" = "COST_ESTIMATED_TOO_EXPENSIVE" + ); + + test_on_execution(include_str!( + "fixtures/enforce_on_execution_response.router.yaml" + )) + .await; + assert_counter!( + "apollo.router.operations.demand_control", + 2, + "demand_control.result" = "COST_ESTIMATED_TOO_EXPENSIVE" + ); + + // The metric should not be published on subgraph requests + test_on_subgraph(include_str!( + "fixtures/enforce_on_subgraph_request.router.yaml" + )) + .await; + test_on_subgraph(include_str!( + "fixtures/enforce_on_subgraph_response.router.yaml" + )) + .await; + assert_counter!( + "apollo.router.operations.demand_control", + 2, + "demand_control.result" = "COST_ESTIMATED_TOO_EXPENSIVE" + ); + } + .with_metrics() + .await + } + async fn test_on_execution(config: &'static str) -> Vec { let plugin = PluginTestHarness::::builder() .config(config) diff --git a/apollo-router/src/plugins/demand_control/strategy/test.rs b/apollo-router/src/plugins/demand_control/strategy/test.rs index f60a5e1443..f50f290e5a 100644 --- a/apollo-router/src/plugins/demand_control/strategy/test.rs +++ b/apollo-router/src/plugins/demand_control/strategy/test.rs @@ -3,6 +3,7 @@ use apollo_compiler::ExecutableDocument; use crate::plugins::demand_control::strategy::StrategyImpl; use crate::plugins::demand_control::test::TestError; use crate::plugins::demand_control::test::TestStage; +use crate::plugins::demand_control::CostContext; use crate::plugins::demand_control::DemandControlError; use crate::services::execution::Request; use crate::services::subgraph::Response; @@ -15,25 +16,29 @@ pub(crate) struct Test { } impl StrategyImpl for Test { - fn on_execution_request(&self, _request: &Request) -> Result<(), DemandControlError> { + fn on_execution_request(&self, request: &Request) -> Result<(), DemandControlError> { + let mut extensions = request.context.extensions().lock(); + let cost_context = extensions.get_or_default_mut::(); match self { Test { stage: TestStage::ExecutionRequest, error, - } => Err(error.into()), + } => Err(cost_context.result(error.into())), _ => Ok(()), } } fn on_subgraph_request( &self, - _request: &crate::services::subgraph::Request, + request: &crate::services::subgraph::Request, ) -> Result<(), DemandControlError> { + let mut extensions = request.context.extensions().lock(); + let cost_context = extensions.get_or_default_mut::(); match self { Test { stage: TestStage::SubgraphRequest, error, - } => Err(error.into()), + } => Err(cost_context.result(error.into())), _ => Ok(()), } } @@ -41,28 +46,32 @@ impl StrategyImpl for Test { fn on_subgraph_response( &self, _request: &ExecutableDocument, - _response: &Response, + response: &Response, ) -> Result<(), DemandControlError> { + let mut extensions = response.context.extensions().lock(); + let cost_context = extensions.get_or_default_mut::(); match self { Test { stage: TestStage::SubgraphResponse, error, - } => Err(error.into()), + } => Err(cost_context.result(error.into())), _ => Ok(()), } } fn on_execution_response( &self, - _context: &crate::Context, + context: &crate::Context, _request: &ExecutableDocument, _response: &crate::graphql::Response, ) -> Result<(), DemandControlError> { + let mut extensions = context.extensions().lock(); + let cost_context = extensions.get_or_default_mut::(); match self { Test { stage: TestStage::ExecutionResponse, error, - } => Err(error.into()), + } => Err(cost_context.result(error.into())), _ => Ok(()), } } diff --git a/apollo-router/src/plugins/rhai/engine.rs b/apollo-router/src/plugins/rhai/engine.rs index eaf288dfbd..7d777a343c 100644 --- a/apollo-router/src/plugins/rhai/engine.rs +++ b/apollo-router/src/plugins/rhai/engine.rs @@ -16,6 +16,7 @@ use http::uri::Parts; use http::uri::PathAndQuery; use http::HeaderMap; use http::Method; +use http::StatusCode; use http::Uri; use rhai::module_resolvers::FileModuleResolver; use rhai::plugin::*; @@ -218,6 +219,40 @@ mod router_method { } } +#[export_module] +mod status_code { + use rhai::INT; + + pub(crate) type StatusCode = http::StatusCode; + + #[rhai_fn(return_raw)] + pub(crate) fn status_code_from_int(number: INT) -> Result> { + let code = StatusCode::from_u16(number as u16).map_err(|e| e.to_string())?; + Ok(code) + } + + #[rhai_fn(name = "to_string", pure)] + pub(crate) fn status_code_to_string(status_code: &mut StatusCode) -> String { + status_code.as_str().to_string() + } + + #[rhai_fn(name = "==", pure)] + pub(crate) fn status_code_equal_comparator( + status_code: &mut StatusCode, + other: StatusCode, + ) -> bool { + status_code == &other + } + + #[rhai_fn(name = "!=", pure)] + pub(crate) fn status_code_not_equal_comparator( + status_code: &mut StatusCode, + other: StatusCode, + ) -> bool { + status_code != &other + } +} + #[export_module] mod router_header_map { pub(crate) type HeaderMap = http::HeaderMap; @@ -1425,6 +1460,13 @@ macro_rules! register_rhai_router_interface { } ); + $engine.register_get( + "status_code", + |obj: &mut SharedMut<$base::Response>| -> Result> { + Ok(obj.with_mut(|response| response.response.status())) + } + ); + $engine.register_set( "uri", |obj: &mut SharedMut<$base::Request>, uri: Uri| { @@ -1460,6 +1502,13 @@ macro_rules! register_rhai_interface { } ); + $engine.register_get( + "status_code", + |obj: &mut SharedMut<$base::Response>| -> Result> { + Ok(obj.with_mut(|response| response.response.status())) + } + ); + $engine.register_set( "context", |obj: &mut SharedMut<$base::Request>, context: Context| { @@ -1632,6 +1681,7 @@ impl Rhai { let mut module = exported_module!(router_plugin); combine_with_exported_module!(&mut module, "header", router_header_map); combine_with_exported_module!(&mut module, "method", router_method); + combine_with_exported_module!(&mut module, "status_code", status_code); combine_with_exported_module!(&mut module, "context", router_context); let base64_module = exported_module!(router_base64); diff --git a/apollo-router/src/plugins/rhai/tests.rs b/apollo-router/src/plugins/rhai/tests.rs index 41a59ced1b..5b1b5fd771 100644 --- a/apollo-router/src/plugins/rhai/tests.rs +++ b/apollo-router/src/plugins/rhai/tests.rs @@ -486,7 +486,11 @@ async fn it_can_process_subgraph_response() { // We must wrap our canned response in Arc>> to keep the rhai runtime // happy - let response = Arc::new(Mutex::new(Some(subgraph::Response::fake_builder().build()))); + let response = Arc::new(Mutex::new(Some( + subgraph::Response::fake_builder() + .status_code(StatusCode::OK) + .build(), + ))); // Call our rhai test function. If it return an error, the test failed. let result: Result<(), Box> = block.engine.call_fn( @@ -725,7 +729,7 @@ async fn it_can_process_string_subgraph_forbidden() { if let Err(error) = base_process_function("process_subgraph_response_string").await { let processed_error = process_error(error); assert_eq!(processed_error.status, StatusCode::INTERNAL_SERVER_ERROR); - assert_eq!(processed_error.message, Some("rhai execution error: 'Runtime error: I have raised an error (line 161, position 5)\nin call to function 'process_subgraph_response_string''".to_string())); + assert_eq!(processed_error.message, Some("rhai execution error: 'Runtime error: I have raised an error (line 170, position 5)\nin call to function 'process_subgraph_response_string''".to_string())); } else { // Test failed panic!("error processed incorrectly"); @@ -751,7 +755,7 @@ async fn it_cannot_process_om_subgraph_missing_message_and_body() { { let processed_error = process_error(error); assert_eq!(processed_error.status, StatusCode::BAD_REQUEST); - assert_eq!(processed_error.message, Some("rhai execution error: 'Runtime error: #{\"status\": 400} (line 172, position 5)\nin call to function 'process_subgraph_response_om_missing_message''".to_string())); + assert_eq!(processed_error.message, Some("rhai execution error: 'Runtime error: #{\"status\": 400} (line 181, position 5)\nin call to function 'process_subgraph_response_om_missing_message''".to_string())); } else { // Test failed panic!("error processed incorrectly"); diff --git a/apollo-router/src/plugins/telemetry/config.rs b/apollo-router/src/plugins/telemetry/config.rs index 3dd3c676c7..7bdc506fb0 100644 --- a/apollo-router/src/plugins/telemetry/config.rs +++ b/apollo-router/src/plugins/telemetry/config.rs @@ -379,12 +379,30 @@ pub(crate) enum AttributeValue { Array(AttributeArray), } +impl From for AttributeValue { + fn from(value: String) -> Self { + AttributeValue::String(value) + } +} + impl From<&'static str> for AttributeValue { fn from(value: &'static str) -> Self { AttributeValue::String(value.to_string()) } } +impl From for AttributeValue { + fn from(value: bool) -> Self { + AttributeValue::Bool(value) + } +} + +impl From for AttributeValue { + fn from(value: f64) -> Self { + AttributeValue::F64(value) + } +} + impl From for AttributeValue { fn from(value: i64) -> Self { AttributeValue::I64(value) @@ -403,6 +421,19 @@ impl std::fmt::Display for AttributeValue { } } +impl PartialOrd for AttributeValue { + fn partial_cmp(&self, other: &Self) -> Option { + match (self, other) { + (AttributeValue::Bool(b1), AttributeValue::Bool(b2)) => b1.partial_cmp(b2), + (AttributeValue::F64(f1), AttributeValue::F64(f2)) => f1.partial_cmp(f2), + (AttributeValue::I64(i1), AttributeValue::I64(i2)) => i1.partial_cmp(i2), + (AttributeValue::String(s1), AttributeValue::String(s2)) => s1.partial_cmp(s2), + // Arrays and mismatched types are incomparable + _ => None, + } + } +} + impl TryFrom for AttributeValue { type Error = (); fn try_from(value: serde_json::Value) -> Result { diff --git a/apollo-router/src/plugins/telemetry/config_new/attributes.rs b/apollo-router/src/plugins/telemetry/config_new/attributes.rs index e4acaa6fb8..889e466fa6 100644 --- a/apollo-router/src/plugins/telemetry/config_new/attributes.rs +++ b/apollo-router/src/plugins/telemetry/config_new/attributes.rs @@ -48,6 +48,7 @@ use crate::services::router; use crate::services::router::Request; use crate::services::subgraph; use crate::services::supergraph; +use crate::Context; pub(crate) const SUBGRAPH_NAME: Key = Key::from_static_str("subgraph.name"); pub(crate) const SUBGRAPH_GRAPHQL_DOCUMENT: Key = Key::from_static_str("subgraph.graphql.document"); @@ -517,6 +518,7 @@ impl DefaultForLevel for HttpServerAttributes { impl Selectors for RouterAttributes { type Request = router::Request; type Response = router::Response; + type EventResponse = (); fn on_request(&self, request: &router::Request) -> Vec { let mut attrs = self.common.on_request(request); @@ -564,6 +566,7 @@ impl Selectors for RouterAttributes { impl Selectors for HttpCommonAttributes { type Request = router::Request; type Response = router::Response; + type EventResponse = (); fn on_request(&self, request: &router::Request) -> Vec { let mut attrs = Vec::new(); @@ -681,6 +684,7 @@ impl Selectors for HttpCommonAttributes { impl Selectors for HttpServerAttributes { type Request = router::Request; type Response = router::Response; + type EventResponse = (); fn on_request(&self, request: &router::Request) -> Vec { let mut attrs = Vec::new(); @@ -857,6 +861,7 @@ impl HttpServerAttributes { impl Selectors for SupergraphAttributes { type Request = supergraph::Request; type Response = supergraph::Response; + type EventResponse = crate::graphql::Response; fn on_request(&self, request: &supergraph::Request) -> Vec { let mut attrs = Vec::new(); @@ -899,6 +904,16 @@ impl Selectors for SupergraphAttributes { attrs } + fn on_response_event( + &self, + response: &Self::EventResponse, + context: &Context, + ) -> Vec { + let mut attrs = Vec::new(); + attrs.append(&mut self.cost.on_response_event(response, context)); + attrs + } + fn on_error(&self, _error: &BoxError) -> Vec { Vec::default() } @@ -907,6 +922,7 @@ impl Selectors for SupergraphAttributes { impl Selectors for SubgraphAttributes { type Request = subgraph::Request; type Response = subgraph::Response; + type EventResponse = (); fn on_request(&self, request: &subgraph::Request) -> Vec { let mut attrs = Vec::new(); diff --git a/apollo-router/src/plugins/telemetry/config_new/conditional.rs b/apollo-router/src/plugins/telemetry/config_new/conditional.rs index 617692214b..124e41b853 100644 --- a/apollo-router/src/plugins/telemetry/config_new/conditional.rs +++ b/apollo-router/src/plugins/telemetry/config_new/conditional.rs @@ -5,7 +5,10 @@ use std::sync::Arc; use parking_lot::Mutex; use schemars::gen::SchemaGenerator; +use schemars::schema::ObjectValidation; use schemars::schema::Schema; +use schemars::schema::SchemaObject; +use schemars::schema::SubschemaValidation; use schemars::JsonSchema; use serde::de::Error; use serde::de::MapAccess; @@ -20,6 +23,7 @@ use crate::plugins::telemetry::config_new::conditions::Condition; use crate::plugins::telemetry::config_new::DefaultForLevel; use crate::plugins::telemetry::config_new::Selector; use crate::plugins::telemetry::otlp::TelemetryDataKind; +use crate::Context; /// The state of the conditional. #[derive(Debug, Default)] @@ -77,26 +81,58 @@ where fn json_schema(gen: &mut SchemaGenerator) -> Schema { // Add condition to each variant in the schema. //Maybe we can rearrange this for a smaller schema - let mut selector = gen.subschema_for::(); - - if let Schema::Object(schema) = &mut selector { - if let Some(object) = &mut schema.subschemas { - if let Some(any_of) = &mut object.any_of { - for mut variant in any_of { - if let Schema::Object(variant) = &mut variant { - if let Some(object) = &mut variant.object { - object.properties.insert( - "condition".to_string(), - gen.subschema_for::>(), - ); - } - } - } - } - } - } - - selector + let selector = gen.subschema_for::(); + + Schema::Object(SchemaObject { + metadata: None, + instance_type: None, + format: None, + enum_values: None, + const_value: None, + subschemas: Some(Box::new(SubschemaValidation { + any_of: Some(vec![ + selector, + Schema::Object(SchemaObject { + metadata: None, + instance_type: None, + format: None, + enum_values: None, + const_value: None, + subschemas: None, + number: None, + string: None, + array: None, + object: Some(Box::new(ObjectValidation { + max_properties: None, + min_properties: None, + required: Default::default(), + properties: [( + "condition".to_string(), + gen.subschema_for::>(), + )] + .into(), + pattern_properties: Default::default(), + additional_properties: None, + property_names: None, + })), + reference: None, + extensions: Default::default(), + }), + ]), + all_of: None, + one_of: None, + not: None, + if_schema: None, + then_schema: None, + else_schema: None, + })), + number: None, + string: None, + array: None, + object: None, + reference: None, + extensions: Default::default(), + }) } } @@ -113,12 +149,13 @@ where } } -impl Selector for Conditional +impl Selector for Conditional where - Att: Selector, + Att: Selector, { type Request = Request; type Response = Response; + type EventResponse = EventResponse; fn on_request(&self, request: &Self::Request) -> Option { match &self.condition { @@ -161,6 +198,40 @@ where } } + fn on_response_event( + &self, + response: &Self::EventResponse, + ctx: &Context, + ) -> Option { + // We may have got the value from the request. + let value = mem::take(&mut *self.value.lock()); + + match (value, &self.condition) { + (State::Value(value), Some(condition)) => { + // We have a value already, let's see if the condition was evaluated to true. + if condition.lock().evaluate_event_response(response, ctx) { + *self.value.lock() = State::Returned; + Some(value) + } else { + None + } + } + (State::Pending | State::Returned, Some(condition)) => { + // We don't have a value already, let's try to get it from the response if the condition was evaluated to true. + if condition.lock().evaluate_event_response(response, ctx) { + self.selector.on_response_event(response, ctx) + } else { + None + } + } + (State::Pending, None) => { + // We don't have a value already, and there is no condition. + self.selector.on_response_event(response, ctx) + } + _ => None, + } + } + fn on_response(&self, response: &Self::Response) -> Option { // We may have got the value from the request. let value = mem::take(&mut *self.value.lock()); @@ -190,6 +261,36 @@ where _ => None, } } + + fn on_error(&self, error: &tower::BoxError) -> Option { + // We may have got the value from the request. + let value = mem::take(&mut *self.value.lock()); + + match (value, &self.condition) { + (State::Value(value), Some(condition)) => { + // We have a value already, let's see if the condition was evaluated to true. + if condition.lock().evaluate_error(error) { + *self.value.lock() = State::Returned; + Some(value) + } else { + None + } + } + (State::Pending, Some(condition)) => { + // We don't have a value already, let's try to get it from the error if the condition was evaluated to true. + if condition.lock().evaluate_error(error) { + self.selector.on_error(error) + } else { + None + } + } + (State::Pending, None) => { + // We don't have a value already, and there is no condition. + self.selector.on_error(error) + } + _ => None, + } + } } /// Custom Deserializer for attributes that will deserialize into a custom field if possible, but otherwise into one of the pre-defined attributes. diff --git a/apollo-router/src/plugins/telemetry/config_new/conditions.rs b/apollo-router/src/plugins/telemetry/config_new/conditions.rs index bfb16eecb8..10903b9bad 100644 --- a/apollo-router/src/plugins/telemetry/config_new/conditions.rs +++ b/apollo-router/src/plugins/telemetry/config_new/conditions.rs @@ -1,9 +1,11 @@ use opentelemetry::Value; use schemars::JsonSchema; use serde::Deserialize; +use tower::BoxError; use crate::plugins::telemetry::config::AttributeValue; use crate::plugins::telemetry::config_new::Selector; +use crate::Context; #[derive(Deserialize, JsonSchema, Clone, Debug)] #[cfg_attr(test, derive(PartialEq))] @@ -11,6 +13,10 @@ use crate::plugins::telemetry::config_new::Selector; pub(crate) enum Condition { /// A condition to check a selection against a value. Eq([SelectorOrValue; 2]), + /// The first selection must be greater than the second selection. + Gt([SelectorOrValue; 2]), + /// The first selection must be less than the second selection. + Lt([SelectorOrValue; 2]), /// A condition to check a selection against a selector. Exists(T), /// All sub-conditions must be true. @@ -72,6 +78,54 @@ where } } }, + Condition::Gt(gt) => { + let left_att = gt[0].on_request(request).map(AttributeValue::from); + let right_att = gt[1].on_request(request).map(AttributeValue::from); + match (left_att, right_att) { + (None, None) => None, + (Some(l), None) => { + gt[0] = SelectorOrValue::Value(l); + None + } + (None, Some(r)) => { + gt[1] = SelectorOrValue::Value(r); + None + } + (Some(l), Some(r)) => { + if l > r { + *self = Condition::True; + Some(true) + } else { + *self = Condition::False; + Some(false) + } + } + } + } + Condition::Lt(lt) => { + let left_att = lt[0].on_request(request).map(AttributeValue::from); + let right_att = lt[1].on_request(request).map(AttributeValue::from); + match (left_att, right_att) { + (None, None) => None, + (Some(l), None) => { + lt[0] = SelectorOrValue::Value(l); + None + } + (None, Some(r)) => { + lt[1] = SelectorOrValue::Value(r); + None + } + (Some(l), Some(r)) => { + if l < r { + *self = Condition::True; + Some(true) + } else { + *self = Condition::False; + Some(false) + } + } + } + } Condition::Exists(exist) => { if exist.on_request(request).is_some() { *self = Condition::True; @@ -124,6 +178,44 @@ where } } + pub(crate) fn evaluate_event_response( + &self, + response: &T::EventResponse, + ctx: &Context, + ) -> bool { + match self { + Condition::Eq(eq) => { + let left = eq[0].on_response_event(response, ctx); + let right = eq[1].on_response_event(response, ctx); + left == right + } + Condition::Gt(gt) => { + let left_att = gt[0] + .on_response_event(response, ctx) + .map(AttributeValue::from); + let right_att = gt[1] + .on_response_event(response, ctx) + .map(AttributeValue::from); + left_att.zip(right_att).map_or(false, |(l, r)| l > r) + } + Condition::Lt(gt) => { + let left_att = gt[0] + .on_response_event(response, ctx) + .map(AttributeValue::from); + let right_att = gt[1] + .on_response_event(response, ctx) + .map(AttributeValue::from); + left_att.zip(right_att).map_or(false, |(l, r)| l < r) + } + Condition::Exists(exist) => exist.on_response_event(response, ctx).is_some(), + Condition::All(all) => all.iter().all(|c| c.evaluate_event_response(response, ctx)), + Condition::Any(any) => any.iter().any(|c| c.evaluate_event_response(response, ctx)), + Condition::Not(not) => !not.evaluate_event_response(response, ctx), + Condition::True => true, + Condition::False => false, + } + } + pub(crate) fn evaluate_response(&self, response: &T::Response) -> bool { match self { Condition::Eq(eq) => { @@ -131,6 +223,16 @@ where let right = eq[1].on_response(response); left == right } + Condition::Gt(gt) => { + let left_att = gt[0].on_response(response).map(AttributeValue::from); + let right_att = gt[1].on_response(response).map(AttributeValue::from); + left_att.zip(right_att).map_or(false, |(l, r)| l > r) + } + Condition::Lt(gt) => { + let left_att = gt[0].on_response(response).map(AttributeValue::from); + let right_att = gt[1].on_response(response).map(AttributeValue::from); + left_att.zip(right_att).map_or(false, |(l, r)| l < r) + } Condition::Exists(exist) => exist.on_response(response).is_some(), Condition::All(all) => all.iter().all(|c| c.evaluate_response(response)), Condition::Any(any) => any.iter().any(|c| c.evaluate_response(response)), @@ -139,6 +241,32 @@ where Condition::False => false, } } + + pub(crate) fn evaluate_error(&self, error: &BoxError) -> bool { + match self { + Condition::Eq(eq) => { + let left = eq[0].on_error(error); + let right = eq[1].on_error(error); + left == right + } + Condition::Gt(gt) => { + let left_att = gt[0].on_error(error).map(AttributeValue::from); + let right_att = gt[1].on_error(error).map(AttributeValue::from); + left_att.zip(right_att).map_or(false, |(l, r)| l > r) + } + Condition::Lt(gt) => { + let left_att = gt[0].on_error(error).map(AttributeValue::from); + let right_att = gt[1].on_error(error).map(AttributeValue::from); + left_att.zip(right_att).map_or(false, |(l, r)| l < r) + } + Condition::Exists(exist) => exist.on_error(error).is_some(), + Condition::All(all) => all.iter().all(|c| c.evaluate_error(error)), + Condition::Any(any) => any.iter().any(|c| c.evaluate_error(error)), + Condition::Not(not) => !not.evaluate_error(error), + Condition::True => true, + Condition::False => false, + } + } } impl Selector for SelectorOrValue @@ -147,6 +275,7 @@ where { type Request = T::Request; type Response = T::Response; + type EventResponse = T::EventResponse; fn on_request(&self, request: &T::Request) -> Option { match self { @@ -161,20 +290,37 @@ where SelectorOrValue::Selector(selector) => selector.on_response(response), } } + + fn on_response_event(&self, response: &T::EventResponse, ctx: &Context) -> Option { + match self { + SelectorOrValue::Value(value) => Some(value.clone().into()), + SelectorOrValue::Selector(selector) => selector.on_response_event(response, ctx), + } + } + + fn on_error(&self, error: &BoxError) -> Option { + match self { + SelectorOrValue::Value(value) => Some(value.clone().into()), + SelectorOrValue::Selector(selector) => selector.on_error(error), + } + } } #[cfg(test)] mod test { use opentelemetry::Value; + use tower::BoxError; use crate::plugins::telemetry::config_new::conditions::Condition; use crate::plugins::telemetry::config_new::conditions::SelectorOrValue; use crate::plugins::telemetry::config_new::Selector; + use crate::Context; struct TestSelector; impl Selector for TestSelector { type Request = Option; type Response = Option; + type EventResponse = Option; fn on_request(&self, request: &Self::Request) -> Option { request.map(Value::I64) @@ -183,6 +329,18 @@ mod test { fn on_response(&self, response: &Self::Response) -> Option { response.map(Value::I64) } + + fn on_error(&self, error: &tower::BoxError) -> Option { + Some(error.to_string().into()) + } + + fn on_response_event( + &self, + response: &Self::EventResponse, + _ctx: &crate::Context, + ) -> Option { + response.map(Value::I64) + } } enum TestSelectorReqRes { @@ -193,6 +351,7 @@ mod test { impl Selector for TestSelectorReqRes { type Request = Option; type Response = Option; + type EventResponse = Option; fn on_request(&self, request: &Self::Request) -> Option { match self { @@ -201,12 +360,27 @@ mod test { } } + fn on_response_event( + &self, + response: &Self::EventResponse, + _ctx: &crate::Context, + ) -> Option { + match self { + TestSelectorReqRes::Req => None, + TestSelectorReqRes::Resp => response.map(Value::I64), + } + } + fn on_response(&self, response: &Self::Response) -> Option { match self { TestSelectorReqRes::Req => None, TestSelectorReqRes::Resp => response.map(Value::I64), } } + + fn on_error(&self, error: &tower::BoxError) -> Option { + Some(error.to_string().into()) + } } #[test] @@ -229,6 +403,10 @@ mod test { Condition::::Exists(TestSelectorReqRes::Resp) .evaluate_response(&Some(3i64)) ); + assert!( + Condition::::Exists(TestSelectorReqRes::Resp) + .evaluate_event_response(&Some(3i64), &Context::new()) + ); } #[test] @@ -251,6 +429,16 @@ mod test { SelectorOrValue::Value(2i64.into()), ]) .evaluate_response(&None)); + assert!(Condition::::Eq([ + SelectorOrValue::Value(1i64.into()), + SelectorOrValue::Value(1i64.into()), + ]) + .evaluate_event_response(&None, &Context::new())); + assert!(!Condition::::Eq([ + SelectorOrValue::Value(1i64.into()), + SelectorOrValue::Value(2i64.into()), + ]) + .evaluate_event_response(&None, &Context::new())); } #[test] @@ -301,11 +489,151 @@ mod test { ]) .evaluate_response(&None)); + assert!(Condition::::Eq([ + SelectorOrValue::Selector(TestSelector), + SelectorOrValue::Value(2i64.into()), + ]) + .evaluate_event_response(&Some(2i64), &Context::new())); + assert!(Condition::::Eq([ + SelectorOrValue::Value(2i64.into()), + SelectorOrValue::Selector(TestSelector), + ]) + .evaluate_event_response(&Some(2i64), &Context::new())); + + assert!(!Condition::::Eq([ + SelectorOrValue::Selector(TestSelector), + SelectorOrValue::Value(3i64.into()), + ]) + .evaluate_event_response(&None, &Context::new())); + assert!(!Condition::::Eq([ + SelectorOrValue::Value(3i64.into()), + SelectorOrValue::Selector(TestSelector), + ]) + .evaluate_event_response(&None, &Context::new())); + assert!(Condition::::Eq([ + SelectorOrValue::Value("my custom error".to_string().into()), + SelectorOrValue::Selector(TestSelector), + ]) + .evaluate_error(&BoxError::from("my custom error"))); + assert!(!Condition::::Eq([ + SelectorOrValue::Value("my custom error".to_string().into()), + SelectorOrValue::Selector(TestSelector), + ]) + .evaluate_error(&BoxError::from("my different custom error"))); + let mut condition = Condition::::Eq([ SelectorOrValue::Value(3i64.into()), SelectorOrValue::Selector(TestSelectorReqRes::Req), ]); assert_eq!(Some(false), condition.evaluate_request(&Some(2i64))); + + assert_eq!( + Some(true), + Condition::::Eq([ + SelectorOrValue::Value("a".into()), + SelectorOrValue::Value("a".into()) + ]) + .evaluate_request(&None) + ); + } + + #[test] + fn test_condition_gt() { + assert_eq!( + Some(true), + Condition::::Gt([ + SelectorOrValue::Value(true.into()), + SelectorOrValue::Value(false.into()) + ]) + .evaluate_request(&None) + ); + + assert_eq!( + Some(false), + Condition::::Gt([ + SelectorOrValue::Value(true.into()), + SelectorOrValue::Value(true.into()) + ]) + .evaluate_request(&None) + ); + + assert_eq!( + Some(true), + Condition::::Gt([ + SelectorOrValue::Value(2f64.into()), + SelectorOrValue::Value(1f64.into()) + ]) + .evaluate_request(&None) + ); + + assert_eq!( + Some(false), + Condition::::Gt([ + SelectorOrValue::Value(1f64.into()), + SelectorOrValue::Value(1f64.into()) + ]) + .evaluate_request(&None) + ); + + assert_eq!( + Some(true), + Condition::::Gt([ + SelectorOrValue::Value(2i64.into()), + SelectorOrValue::Value(1i64.into()) + ]) + .evaluate_request(&None) + ); + + assert_eq!( + Some(false), + Condition::::Gt([ + SelectorOrValue::Value(1i64.into()), + SelectorOrValue::Value(1i64.into()) + ]) + .evaluate_request(&None) + ); + + assert_eq!( + Some(true), + Condition::::Gt([ + SelectorOrValue::Value("b".into()), + SelectorOrValue::Value("a".into()) + ]) + .evaluate_request(&None) + ); + + assert_eq!( + Some(false), + Condition::::Gt([ + SelectorOrValue::Value("a".into()), + SelectorOrValue::Value("a".into()) + ]) + .evaluate_request(&None) + ); + + assert_eq!( + Some(true), + Condition::::Gt([ + SelectorOrValue::Value(2i64.into()), + SelectorOrValue::Selector(TestSelector) + ]) + .evaluate_request(&Some(1i64)) + ); + + assert_eq!( + Some(false), + Condition::::Gt([ + SelectorOrValue::Selector(TestSelector), + SelectorOrValue::Value(1i64.into()) + ]) + .evaluate_request(&Some(1i64)) + ); + + assert!(Condition::::Gt([ + SelectorOrValue::Value(2i64.into()), + SelectorOrValue::Value(1i64.into()) + ]) + .evaluate_response(&None)); } #[test] @@ -321,6 +649,17 @@ mod test { SelectorOrValue::Value(1i64.into()), ]))) .evaluate_response(&None)); + assert!(Condition::::Not(Box::new(Condition::Eq([ + SelectorOrValue::Value(1i64.into()), + SelectorOrValue::Value(2i64.into()), + ]))) + .evaluate_event_response(&None, &Context::new())); + + assert!(!Condition::::Not(Box::new(Condition::Eq([ + SelectorOrValue::Value(1i64.into()), + SelectorOrValue::Value(1i64.into()), + ]))) + .evaluate_event_response(&None, &Context::new())); } #[test] @@ -336,6 +675,17 @@ mod test { ]) ]) .evaluate_response(&None)); + assert!(Condition::::All(vec![ + Condition::Eq([ + SelectorOrValue::Value(1i64.into()), + SelectorOrValue::Value(1i64.into()), + ]), + Condition::Eq([ + SelectorOrValue::Value(2i64.into()), + SelectorOrValue::Value(2i64.into()), + ]) + ]) + .evaluate_event_response(&None, &Context::new())); let mut condition = Condition::::All(vec![ Condition::Eq([ @@ -350,6 +700,7 @@ mod test { assert!(condition.evaluate_request(&Some(1i64)).is_none()); assert!(condition.evaluate_response(&Some(3i64))); + assert!(condition.evaluate_event_response(&Some(3i64), &Context::new())); let mut condition = Condition::::All(vec![ Condition::Eq([ @@ -364,6 +715,7 @@ mod test { assert!(condition.evaluate_request(&Some(1i64)).is_none()); assert!(!condition.evaluate_response(&Some(2i64))); + assert!(!condition.evaluate_event_response(&Some(2i64), &Context::new())); let mut condition = Condition::::All(vec![ Condition::Eq([ @@ -378,6 +730,7 @@ mod test { assert_eq!(Some(false), condition.evaluate_request(&Some(1i64))); assert!(!condition.evaluate_response(&Some(2i64))); + assert!(!condition.evaluate_event_response(&Some(2i64), &Context::new())); assert!(!Condition::::All(vec![ Condition::Eq([ @@ -390,6 +743,18 @@ mod test { ]) ]) .evaluate_response(&None)); + + assert!(!Condition::::All(vec![ + Condition::Eq([ + SelectorOrValue::Value(1i64.into()), + SelectorOrValue::Value(1i64.into()), + ]), + Condition::Eq([ + SelectorOrValue::Value(1i64.into()), + SelectorOrValue::Value(2i64.into()), + ]) + ]) + .evaluate_event_response(&None, &Context::new())); } #[test] @@ -417,5 +782,28 @@ mod test { ]) ]) .evaluate_response(&None)); + assert!(Condition::::Any(vec![ + Condition::Eq([ + SelectorOrValue::Value(1i64.into()), + SelectorOrValue::Value(1i64.into()), + ]), + Condition::Eq([ + SelectorOrValue::Value(1i64.into()), + SelectorOrValue::Value(2i64.into()), + ]) + ]) + .evaluate_event_response(&None, &Context::new())); + + assert!(!Condition::::All(vec![ + Condition::Eq([ + SelectorOrValue::Value(1i64.into()), + SelectorOrValue::Value(2i64.into()), + ]), + Condition::Eq([ + SelectorOrValue::Value(1i64.into()), + SelectorOrValue::Value(2i64.into()), + ]) + ]) + .evaluate_event_response(&None, &Context::new())); } } diff --git a/apollo-router/src/plugins/telemetry/config_new/cost/fixtures/cost_delta_with_attributes.router.yaml b/apollo-router/src/plugins/telemetry/config_new/cost/fixtures/cost_delta_with_attributes.router.yaml index 244bd2181d..643697a4ac 100644 --- a/apollo-router/src/plugins/telemetry/config_new/cost/fixtures/cost_delta_with_attributes.router.yaml +++ b/apollo-router/src/plugins/telemetry/config_new/cost/fixtures/cost_delta_with_attributes.router.yaml @@ -4,4 +4,5 @@ telemetry: supergraph: cost.delta: attributes: + graphql.operation.name: true cost.result: true \ No newline at end of file diff --git a/apollo-router/src/plugins/telemetry/config_new/cost/mod.rs b/apollo-router/src/plugins/telemetry/config_new/cost/mod.rs index 57f96f6e17..8a897daa6a 100644 --- a/apollo-router/src/plugins/telemetry/config_new/cost/mod.rs +++ b/apollo-router/src/plugins/telemetry/config_new/cost/mod.rs @@ -7,6 +7,7 @@ use schemars::JsonSchema; use serde::Deserialize; use tower::BoxError; +use super::instruments::Increment; use crate::metrics; use crate::plugins::demand_control::CostContext; use crate::plugins::telemetry::config_new::attributes::SupergraphAttributes; @@ -15,7 +16,6 @@ use crate::plugins::telemetry::config_new::extendable::Extendable; use crate::plugins::telemetry::config_new::instruments::CustomHistogram; use crate::plugins::telemetry::config_new::instruments::CustomHistogramInner; use crate::plugins::telemetry::config_new::instruments::DefaultedStandardInstrument; -use crate::plugins::telemetry::config_new::instruments::Increment::Unit; use crate::plugins::telemetry::config_new::instruments::Instrumented; use crate::plugins::telemetry::config_new::selectors::SupergraphSelector; use crate::plugins::telemetry::config_new::Selectors; @@ -49,14 +49,24 @@ pub(crate) struct SupergraphCostAttributes { impl Selectors for SupergraphCostAttributes { type Request = supergraph::Request; type Response = supergraph::Response; + type EventResponse = crate::graphql::Response; fn on_request(&self, _request: &Self::Request) -> Vec { Vec::default() } - fn on_response(&self, response: &Self::Response) -> Vec { + fn on_response(&self, _response: &Self::Response) -> Vec { + Vec::default() + } + + fn on_error(&self, _error: &BoxError) -> Vec { + Vec::default() + } + + fn on_response_event(&self, _response: &Self::EventResponse, ctx: &Context) -> Vec { let mut attrs = Vec::with_capacity(4); - if let Some(cost_result) = &response.context.extensions().lock().get::() { + let cost_result = ctx.extensions().lock().get::().cloned(); + if let Some(cost_result) = cost_result { if let Some(true) = self.cost_estimated { attrs.push(KeyValue::new("cost.estimated", cost_result.estimated)); } @@ -72,10 +82,6 @@ impl Selectors for SupergraphCostAttributes { } attrs } - - fn on_error(&self, _error: &BoxError) -> Vec { - Vec::default() - } } #[derive(Deserialize, JsonSchema, Clone, Default, Debug)] @@ -148,14 +154,16 @@ impl CostInstrumentsConfig { Some(attributes.clone()) } }; + CustomHistogram { inner: Mutex::new(CustomHistogramInner { - increment: Unit, + increment: Increment::EventCustom(None), condition: Condition::True, histogram: Some(meter.f64_histogram(name).init()), attributes: Vec::with_capacity(nb_attributes), selector: Some(Arc::new(selector)), selectors, + updated: false, }), } } @@ -197,6 +205,7 @@ pub(crate) struct CostInstruments { impl Instrumented for CostInstruments { type Request = supergraph::Request; type Response = supergraph::Response; + type EventResponse = crate::graphql::Response; fn on_request(&self, request: &Self::Request) { if let Some(cost_estimated) = &self.cost_estimated { @@ -233,6 +242,18 @@ impl Instrumented for CostInstruments { cost_delta.on_error(error, ctx); } } + + fn on_response_event(&self, response: &Self::EventResponse, ctx: &Context) { + if let Some(cost_estimated) = &self.cost_estimated { + cost_estimated.on_response_event(response, ctx); + } + if let Some(cost_actual) = &self.cost_actual { + cost_actual.on_response_event(response, ctx); + } + if let Some(cost_delta) = &self.cost_delta { + cost_delta.on_response_event(response, ctx); + } + } } #[derive(Deserialize, JsonSchema, Clone, Debug, PartialEq)] @@ -250,6 +271,7 @@ pub(crate) enum CostValue { #[cfg(test)] mod test { + use crate::context::OPERATION_NAME; use crate::plugins::demand_control::CostContext; use crate::plugins::telemetry::config_new::cost::CostInstruments; use crate::plugins::telemetry::config_new::cost::CostInstrumentsConfig; @@ -261,27 +283,36 @@ mod test { fn test_default_estimated() { let config = config(include_str!("fixtures/cost_estimated.router.yaml")); let instruments = config.to_instruments(); - make_request(instruments); + make_request(&instruments); assert_histogram_sum!("cost.estimated", 100.0); + make_request(&instruments); + + assert_histogram_sum!("cost.estimated", 200.0); } #[test] fn test_default_actual() { let config = config(include_str!("fixtures/cost_actual.router.yaml")); let instruments = config.to_instruments(); - make_request(instruments); + make_request(&instruments); assert_histogram_sum!("cost.actual", 10.0); + make_request(&instruments); + + assert_histogram_sum!("cost.actual", 20.0); } #[test] fn test_default_delta() { let config = config(include_str!("fixtures/cost_delta.router.yaml")); let instruments = config.to_instruments(); - make_request(instruments); + make_request(&instruments); assert_histogram_sum!("cost.delta", 90.0); + make_request(&instruments); + + assert_histogram_sum!("cost.delta", 180.0); } #[test] @@ -290,9 +321,12 @@ mod test { "fixtures/cost_estimated_with_attributes.router.yaml" )); let instruments = config.to_instruments(); - make_request(instruments); + make_request(&instruments); assert_histogram_sum!("cost.estimated", 100.0, cost.result = "COST_TOO_EXPENSIVE"); + make_request(&instruments); + + assert_histogram_sum!("cost.estimated", 200.0, cost.result = "COST_TOO_EXPENSIVE"); } #[test] @@ -301,9 +335,12 @@ mod test { "fixtures/cost_actual_with_attributes.router.yaml" )); let instruments = config.to_instruments(); - make_request(instruments); + make_request(&instruments); assert_histogram_sum!("cost.actual", 10.0, cost.result = "COST_TOO_EXPENSIVE"); + make_request(&instruments); + + assert_histogram_sum!("cost.actual", 20.0, cost.result = "COST_TOO_EXPENSIVE"); } #[test] @@ -312,9 +349,22 @@ mod test { "fixtures/cost_delta_with_attributes.router.yaml" )); let instruments = config.to_instruments(); - make_request(instruments); + make_request(&instruments); + + assert_histogram_sum!( + "cost.delta", + 90.0, + cost.result = "COST_TOO_EXPENSIVE", + graphql.operation.name = "Test" + ); - assert_histogram_sum!("cost.delta", 90.0, cost.result = "COST_TOO_EXPENSIVE"); + make_request(&instruments); + assert_histogram_sum!( + "cost.delta", + 180.0, + cost.result = "COST_TOO_EXPENSIVE", + graphql.operation.name = "Test" + ); } fn config(config: &'static str) -> CostInstrumentsConfig { @@ -325,7 +375,7 @@ mod test { .expect("config") } - fn make_request(instruments: CostInstruments) { + fn make_request(instruments: &CostInstruments) { let context = Context::new(); { let mut extensions = context.extensions().lock(); @@ -335,6 +385,7 @@ mod test { cost_result.actual = 10.0; cost_result.result = "COST_TOO_EXPENSIVE" } + let _ = context.insert(OPERATION_NAME, "Test".to_string()).unwrap(); instruments.on_request( &supergraph::Request::fake_builder() .context(context.clone()) @@ -343,9 +394,11 @@ mod test { ); instruments.on_response( &supergraph::Response::fake_builder() - .context(context) + .context(context.clone()) .build() .expect("response"), ); + + instruments.on_response_event(&crate::graphql::Response::default(), &context); } } diff --git a/apollo-router/src/plugins/telemetry/config_new/events.rs b/apollo-router/src/plugins/telemetry/config_new/events.rs index b558ac73cb..e5ea7ebf8e 100644 --- a/apollo-router/src/plugins/telemetry/config_new/events.rs +++ b/apollo-router/src/plugins/telemetry/config_new/events.rs @@ -46,15 +46,18 @@ impl Events { .router .custom .iter() - .map(|(event_name, event_cfg)| CustomEvent { - inner: Mutex::new(CustomEventInner { - name: event_name.clone(), - level: event_cfg.level, - event_on: event_cfg.on, - message: event_cfg.message.clone(), - selectors: event_cfg.attributes.clone().into(), - condition: event_cfg.condition.clone(), - attributes: Vec::new(), + .filter_map(|(event_name, event_cfg)| match &event_cfg.level { + EventLevel::Off => None, + _ => Some(CustomEvent { + inner: Mutex::new(CustomEventInner { + name: event_name.clone(), + level: event_cfg.level, + event_on: event_cfg.on, + message: event_cfg.message.clone(), + selectors: event_cfg.attributes.clone().into(), + condition: event_cfg.condition.clone(), + attributes: Vec::new(), + }), }), }) .collect(); @@ -72,15 +75,18 @@ impl Events { .supergraph .custom .iter() - .map(|(event_name, event_cfg)| CustomEvent { - inner: Mutex::new(CustomEventInner { - name: event_name.clone(), - level: event_cfg.level, - event_on: event_cfg.on, - message: event_cfg.message.clone(), - selectors: event_cfg.attributes.clone().into(), - condition: event_cfg.condition.clone(), - attributes: Vec::new(), + .filter_map(|(event_name, event_cfg)| match &event_cfg.level { + EventLevel::Off => None, + _ => Some(CustomEvent { + inner: Mutex::new(CustomEventInner { + name: event_name.clone(), + level: event_cfg.level, + event_on: event_cfg.on, + message: event_cfg.message.clone(), + selectors: event_cfg.attributes.clone().into(), + condition: event_cfg.condition.clone(), + attributes: Vec::new(), + }), }), }) .collect(); @@ -98,15 +104,18 @@ impl Events { .subgraph .custom .iter() - .map(|(event_name, event_cfg)| CustomEvent { - inner: Mutex::new(CustomEventInner { - name: event_name.clone(), - level: event_cfg.level, - event_on: event_cfg.on, - message: event_cfg.message.clone(), - selectors: event_cfg.attributes.clone().into(), - condition: event_cfg.condition.clone(), - attributes: Vec::new(), + .filter_map(|(event_name, event_cfg)| match &event_cfg.level { + EventLevel::Off => None, + _ => Some(CustomEvent { + inner: Mutex::new(CustomEventInner { + name: event_name.clone(), + level: event_cfg.level, + event_on: event_cfg.on, + message: event_cfg.message.clone(), + selectors: event_cfg.attributes.clone().into(), + condition: event_cfg.condition.clone(), + attributes: Vec::new(), + }), }), }) .collect(); @@ -149,6 +158,7 @@ impl Instrumented { type Request = router::Request; type Response = router::Response; + type EventResponse = (); fn on_request(&self, request: &Self::Request) { if self.request != EventLevel::Off { @@ -251,6 +261,7 @@ impl Instrumented { type Request = supergraph::Request; type Response = supergraph::Response; + type EventResponse = crate::graphql::Response; fn on_request(&self, request: &Self::Request) { if self.request != EventLevel::Off { @@ -304,6 +315,12 @@ impl Instrumented } } + fn on_response_event(&self, response: &Self::EventResponse, ctx: &Context) { + for custom_event in &self.custom { + custom_event.on_response_event(response, ctx); + } + } + fn on_error(&self, error: &BoxError, ctx: &Context) { if self.error != EventLevel::Off { let mut attrs = HashMap::with_capacity(1); @@ -321,6 +338,7 @@ impl Instrumented { type Request = subgraph::Request; type Response = subgraph::Response; + type EventResponse = (); fn on_request(&self, request: &Self::Request) { if self.request != EventLevel::Off { @@ -446,6 +464,8 @@ pub(crate) enum EventOn { Request, /// Log the event on response Response, + /// Log the event on every chunks in the response + EventResponse, /// Log the event on error Error, } @@ -472,13 +492,16 @@ where attributes: Vec, } -impl Instrumented for CustomEvent +impl Instrumented for CustomEvent where - A: Selectors + Default, - T: Selector + Debug + Debug, + A: Selectors + Default, + T: Selector + + Debug + + Debug, { type Request = Request; type Response = Response; + type EventResponse = EventResponse; fn on_request(&self, request: &Self::Request) { let mut inner = self.inner.lock(); @@ -515,6 +538,23 @@ where inner.send_event(); } + fn on_response_event(&self, response: &Self::EventResponse, ctx: &Context) { + let mut inner = self.inner.lock(); + if inner.event_on != EventOn::EventResponse { + return; + } + + if !inner.condition.evaluate_event_response(response, ctx) { + return; + } + if let Some(selectors) = &inner.selectors { + let mut new_attributes = selectors.on_response_event(response, ctx); + inner.attributes.append(&mut new_attributes); + } + + inner.send_event(); + } + fn on_error(&self, error: &BoxError, _ctx: &Context) { let mut inner = self.inner.lock(); if inner.event_on != EventOn::Error { diff --git a/apollo-router/src/plugins/telemetry/config_new/extendable.rs b/apollo-router/src/plugins/telemetry/config_new/extendable.rs index ac00b72566..21c1333d47 100644 --- a/apollo-router/src/plugins/telemetry/config_new/extendable.rs +++ b/apollo-router/src/plugins/telemetry/config_new/extendable.rs @@ -1,4 +1,5 @@ use std::any::type_name; +use std::collections::BTreeMap; use std::collections::HashMap; use std::fmt::Debug; use std::sync::Arc; @@ -21,6 +22,7 @@ use crate::plugins::telemetry::config_new::DefaultForLevel; use crate::plugins::telemetry::config_new::Selector; use crate::plugins::telemetry::config_new::Selectors; use crate::plugins::telemetry::otlp::TelemetryDataKind; +use crate::Context; /// This struct can be used as an attributes container, it has a custom JsonSchema implementation that will merge the schemas of the attributes and custom fields. #[derive(Clone, Debug)] @@ -132,16 +134,37 @@ where } fn json_schema(gen: &mut SchemaGenerator) -> Schema { - let mut attributes = gen.subschema_for::(); + // Extendable json schema is composed of and anyOf of A and additional properties of E + // To allow this to happen we need to generate a schema that contains all the properties of A + // and a schema ref to A. + // We can then add additional properties to the schema of type E. + + let attributes = gen.subschema_for::(); let custom = gen.subschema_for::>(); - if let Schema::Object(schema) = &mut attributes { - if let Some(object) = &mut schema.object { - object.additional_properties = - custom.into_object().object().additional_properties.clone(); + + // Get a list of properties from the attributes schema + let attribute_schema = gen + .dereference(&attributes) + .expect("failed to dereference attributes"); + let mut properties = BTreeMap::new(); + if let Schema::Object(schema_object) = attribute_schema { + if let Some(object_validation) = &schema_object.object { + for key in object_validation.properties.keys() { + properties.insert(key.clone(), Schema::Bool(true)); + } } } - - attributes + let mut schema = attribute_schema.clone(); + if let Schema::Object(schema_object) = &mut schema { + if let Some(object_validation) = &mut schema_object.object { + object_validation.additional_properties = custom + .into_object() + .object + .expect("could not get obejct validation") + .additional_properties; + } + } + schema } } @@ -157,13 +180,14 @@ where } } -impl Selectors for Extendable +impl Selectors for Extendable where - A: Default + Selectors, - E: Selector, + A: Default + Selectors, + E: Selector, { type Request = Request; type Response = Response; + type EventResponse = EventResponse; fn on_request(&self, request: &Self::Request) -> Vec { let mut attrs = self.attributes.on_request(request); @@ -192,6 +216,18 @@ where fn on_error(&self, error: &BoxError) -> Vec { self.attributes.on_error(error) } + + fn on_response_event(&self, response: &Self::EventResponse, ctx: &Context) -> Vec { + let mut attrs = self.attributes.on_response_event(response, ctx); + let custom_attributes = self.custom.iter().filter_map(|(key, value)| { + value + .on_response_event(response, ctx) + .map(|v| KeyValue::new(key.clone(), v)) + }); + attrs.extend(custom_attributes); + + attrs + } } #[cfg(test)] diff --git a/apollo-router/src/plugins/telemetry/config_new/instruments.rs b/apollo-router/src/plugins/telemetry/config_new/instruments.rs index 543dde664c..36c1c7d6f2 100644 --- a/apollo-router/src/plugins/telemetry/config_new/instruments.rs +++ b/apollo-router/src/plugins/telemetry/config_new/instruments.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use std::fmt::Debug; use std::sync::Arc; +use opentelemetry::metrics::Unit; use opentelemetry_api::metrics::Counter; use opentelemetry_api::metrics::Histogram; use opentelemetry_api::metrics::MeterProvider; @@ -41,7 +42,6 @@ use crate::Context; pub(crate) const METER_NAME: &str = "apollo/router"; -#[allow(dead_code)] #[derive(Clone, Deserialize, JsonSchema, Debug, Default)] #[serde(deny_unknown_fields, default)] pub(crate) struct InstrumentsConfig { @@ -94,6 +94,7 @@ impl InstrumentsConfig { Some(attributes.clone()) } }, + updated: false, }), }); let http_server_request_body_size = @@ -125,6 +126,7 @@ impl InstrumentsConfig { default: None, })), selectors, + updated: false, }), } }); @@ -158,6 +160,7 @@ impl InstrumentsConfig { default: None, })), selectors, + updated: false, }), } }); @@ -225,6 +228,7 @@ impl InstrumentsConfig { attributes: Vec::with_capacity(nb_attributes), selector: None, selectors, + updated: false, }), } }); @@ -257,6 +261,7 @@ impl InstrumentsConfig { default: None, })), selectors, + updated: false, }), } }); @@ -289,6 +294,7 @@ impl InstrumentsConfig { default: None, })), selectors, + updated: false, }), } }); @@ -344,12 +350,16 @@ impl DefaultForLevel for RouterInstrumentsConfig { #[derive(Clone, Deserialize, JsonSchema, Debug, Default)] #[serde(deny_unknown_fields, default)] pub(crate) struct ActiveRequestsAttributes { + /// The HTTP request method #[serde(rename = "http.request.method")] http_request_method: bool, + /// The server address #[serde(rename = "server.address")] server_address: bool, + /// The server port #[serde(rename = "server.port")] server_port: bool, + /// The URL scheme #[serde(rename = "url.scheme")] url_scheme: bool, } @@ -432,36 +442,43 @@ where } } -impl Selectors for DefaultedStandardInstrument +impl Selectors for DefaultedStandardInstrument where - T: Selectors, + T: Selectors, { type Request = Request; type Response = Response; + type EventResponse = EventResponse; fn on_request(&self, request: &Self::Request) -> Vec { match self { - Self::Bool(_) | Self::Unset => Vec::new(), + Self::Bool(_) | Self::Unset => Vec::with_capacity(0), Self::Extendable { attributes } => attributes.on_request(request), } } fn on_response(&self, response: &Self::Response) -> Vec { match self { - Self::Bool(_) | Self::Unset => Vec::new(), + Self::Bool(_) | Self::Unset => Vec::with_capacity(0), Self::Extendable { attributes } => attributes.on_response(response), } } fn on_error(&self, error: &BoxError) -> Vec { match self { - Self::Bool(_) | Self::Unset => Vec::new(), + Self::Bool(_) | Self::Unset => Vec::with_capacity(0), Self::Extendable { attributes } => attributes.on_error(error), } } + + fn on_response_event(&self, response: &Self::EventResponse, ctx: &Context) -> Vec { + match self { + Self::Bool(_) | Self::Unset => Vec::with_capacity(0), + Self::Extendable { attributes } => attributes.on_response_event(response, ctx), + } + } } -#[allow(dead_code)] #[derive(Clone, Deserialize, JsonSchema, Debug, Default)] #[serde(deny_unknown_fields, default)] pub(crate) struct SupergraphInstrumentsConfig { @@ -478,7 +495,6 @@ impl DefaultForLevel for SupergraphInstrumentsConfig { } } -#[allow(dead_code)] #[derive(Clone, Deserialize, JsonSchema, Debug, Default)] #[serde(deny_unknown_fields, default)] pub(crate) struct SubgraphInstrumentsConfig { @@ -513,8 +529,8 @@ impl DefaultForLevel for SubgraphInstrumentsConfig { } } -#[allow(dead_code)] #[derive(Clone, Deserialize, JsonSchema, Debug)] +#[serde(deny_unknown_fields)] pub(crate) struct Instrument where A: Default + Debug, @@ -542,14 +558,16 @@ where condition: Condition, } -impl Selectors for Instrument +impl Selectors for Instrument where - A: Debug + Default + Selectors, - E: Debug + Selector, + A: Debug + + Default + + Selectors, + E: Debug + Selector, { type Request = Request; - type Response = Response; + type EventResponse = EventResponse; fn on_request(&self, request: &Self::Request) -> Vec { self.attributes.on_request(request) @@ -559,12 +577,19 @@ where self.attributes.on_response(response) } + fn on_response_event( + &self, + response: &Self::EventResponse, + ctx: &Context, + ) -> Vec { + self.attributes.on_response_event(response, ctx) + } + fn on_error(&self, error: &BoxError) -> Vec { self.attributes.on_error(error) } } -#[allow(dead_code)] #[derive(Clone, Deserialize, JsonSchema, Debug)] #[serde(deny_unknown_fields, rename_all = "snake_case")] pub(crate) enum InstrumentType { @@ -579,15 +604,14 @@ pub(crate) enum InstrumentType { // Gauge, } -#[allow(dead_code)] #[derive(Clone, Deserialize, JsonSchema, Debug)] #[serde(deny_unknown_fields, rename_all = "snake_case", untagged)] pub(crate) enum InstrumentValue { Standard(Standard), + Chunked(Event), Custom(T), } -#[allow(dead_code)] #[derive(Clone, Deserialize, JsonSchema, Debug)] #[serde(deny_unknown_fields, rename_all = "snake_case")] pub(crate) enum Standard { @@ -596,23 +620,43 @@ pub(crate) enum Standard { // Active, } +#[derive(Clone, Deserialize, JsonSchema, Debug)] +#[serde(deny_unknown_fields, rename_all = "snake_case")] +pub(crate) enum Event { + /// For every supergraph response payload (including subscription events and defer events) + #[serde(rename = "event_duration")] + Duration, + /// For every supergraph response payload (including subscription events and defer events) + #[serde(rename = "event_unit")] + Unit, + /// For every supergraph response payload (including subscription events and defer events) + #[serde(rename = "event_custom")] + Custom(T), +} + pub(crate) trait Instrumented { type Request; type Response; + type EventResponse; fn on_request(&self, request: &Self::Request); fn on_response(&self, response: &Self::Response); + fn on_response_event(&self, _response: &Self::EventResponse, _ctx: &Context) {} fn on_error(&self, error: &BoxError, ctx: &Context); } -impl Instrumented for Extendable> +impl Instrumented for Extendable> where - A: Default + Instrumented, - B: Default + Debug + Selectors, - E: Debug + Selector, + A: Default + + Instrumented, + B: Default + + Debug + + Selectors, + E: Debug + Selector, { type Request = Request; type Response = Response; + type EventResponse = EventResponse; fn on_request(&self, request: &Self::Request) { self.attributes.on_request(request); @@ -622,6 +666,10 @@ where self.attributes.on_response(response); } + fn on_response_event(&self, response: &Self::EventResponse, ctx: &Context) { + self.attributes.on_response_event(response, ctx); + } + fn on_error(&self, error: &BoxError, ctx: &Context) { self.attributes.on_error(error, ctx); } @@ -630,6 +678,7 @@ where impl Selectors for SubgraphInstrumentsConfig { type Request = subgraph::Request; type Response = subgraph::Response; + type EventResponse = (); fn on_request(&self, request: &Self::Request) -> Vec { let mut attrs = self.http_client_request_body_size.on_request(request); @@ -689,14 +738,29 @@ where InstrumentValue::Custom(selector) => { (Some(Arc::new(selector.clone())), Increment::Custom(None)) } + InstrumentValue::Chunked(incr) => match incr { + Event::Duration => (None, Increment::EventDuration(Instant::now())), + Event::Unit => (None, Increment::EventUnit), + Event::Custom(selector) => ( + Some(Arc::new(selector.clone())), + Increment::EventCustom(None), + ), + }, }; let counter = CustomCounterInner { increment, condition: instrument.condition.clone(), - counter: Some(meter.f64_counter(instrument_name.clone()).init()), + counter: Some( + meter + .f64_counter(instrument_name.clone()) + .with_description(instrument.description.clone()) + .with_unit(Unit::new(instrument.unit.clone())) + .init(), + ), attributes: Vec::new(), selector, selectors: instrument.attributes.clone(), + incremented: false, }; counters.push(CustomCounter { @@ -715,14 +779,29 @@ where InstrumentValue::Custom(selector) => { (Some(Arc::new(selector.clone())), Increment::Custom(None)) } + InstrumentValue::Chunked(incr) => match incr { + Event::Duration => (None, Increment::EventDuration(Instant::now())), + Event::Unit => (None, Increment::EventUnit), + Event::Custom(selector) => ( + Some(Arc::new(selector.clone())), + Increment::EventCustom(None), + ), + }, }; let histogram = CustomHistogramInner { increment, condition: instrument.condition.clone(), - histogram: Some(meter.f64_histogram(instrument_name.clone()).init()), + histogram: Some( + meter + .f64_histogram(instrument_name.clone()) + .with_description(instrument.description.clone()) + .with_unit(Unit::new(instrument.unit.clone())) + .init(), + ), attributes: Vec::new(), selector, selectors: Some(instrument.attributes.clone()), + updated: false, }; histograms.push(CustomHistogram { @@ -739,14 +818,16 @@ where } } -impl Instrumented +impl Instrumented for CustomInstruments where - Attributes: Selectors + Default, - Select: Selector + Debug, + Attributes: + Selectors + Default, + Select: Selector + Debug, { type Request = Request; type Response = Response; + type EventResponse = EventResponse; fn on_request(&self, request: &Self::Request) { for counter in &self.counters { @@ -774,6 +855,15 @@ where histogram.on_error(error, ctx); } } + + fn on_response_event(&self, response: &Self::EventResponse, ctx: &Context) { + for counter in &self.counters { + counter.on_response_event(response, ctx); + } + for histogram in &self.histograms { + histogram.on_response_event(response, ctx); + } + } } pub(crate) struct RouterInstruments { @@ -792,8 +882,8 @@ pub(crate) struct RouterInstruments { impl Instrumented for RouterInstruments { type Request = router::Request; - type Response = router::Response; + type EventResponse = (); fn on_request(&self, request: &Self::Request) { if let Some(http_server_request_duration) = &self.http_server_request_duration { @@ -852,6 +942,7 @@ pub(crate) struct SupergraphInstruments { impl Instrumented for SupergraphInstruments { type Request = supergraph::Request; type Response = supergraph::Response; + type EventResponse = crate::graphql::Response; fn on_request(&self, request: &Self::Request) { self.cost.on_request(request); @@ -867,6 +958,11 @@ impl Instrumented for SupergraphInstruments { self.cost.on_error(error, ctx); self.custom.on_error(error, ctx); } + + fn on_response_event(&self, response: &Self::EventResponse, ctx: &Context) { + self.cost.on_response_event(response, ctx); + self.custom.on_response_event(response, ctx); + } } pub(crate) struct SubgraphInstruments { @@ -899,8 +995,8 @@ pub(crate) struct SubgraphInstruments { impl Instrumented for SubgraphInstruments { type Request = subgraph::Request; - type Response = subgraph::Response; + type EventResponse = (); fn on_request(&self, request: &Self::Request) { if let Some(http_client_request_duration) = &self.http_client_request_duration { @@ -956,10 +1052,14 @@ pub(crate) type SubgraphCustomInstruments = CustomInstruments; // ---------------- Counter ----------------------- +#[derive(Debug)] pub(crate) enum Increment { Unit, + EventUnit, Duration(Instant), + EventDuration(Instant), Custom(Option), + EventCustom(Option), } struct CustomCounter @@ -981,15 +1081,20 @@ where counter: Option>, condition: Condition, attributes: Vec, + // Useful when it's a counter on events to know if we have to count for an event or not + incremented: bool, } -impl Instrumented for CustomCounter +impl Instrumented for CustomCounter where - A: Selectors + Default, - T: Selector + Debug + Debug, + A: Selectors + Default, + T: Selector + + Debug + + Debug, { type Request = Request; type Response = Response; + type EventResponse = EventResponse; fn on_request(&self, request: &Self::Request) { let mut inner = self.inner.lock(); @@ -999,25 +1104,54 @@ where } inner.attributes = inner.selectors.on_request(request).into_iter().collect(); if let Some(selected_value) = inner.selector.as_ref().and_then(|s| s.on_request(request)) { - inner.increment = Increment::Custom(selected_value.as_str().parse::().ok()) + let new_incr = match &inner.increment { + Increment::EventCustom(None) => { + Increment::EventCustom(selected_value.as_str().parse::().ok()) + } + Increment::Custom(None) => { + Increment::Custom(selected_value.as_str().parse::().ok()) + } + other => { + failfast_error!("this is a bug and should not happen, the increment should only be Custom or EventCustom, please open an issue: {other:?}"); + return; + } + }; + inner.increment = new_incr; } } fn on_response(&self, response: &Self::Response) { let mut inner = self.inner.lock(); if !inner.condition.evaluate_response(response) { - let _ = inner.counter.take(); + if !matches!( + &inner.increment, + Increment::EventCustom(_) | Increment::EventDuration(_) | Increment::EventUnit + ) { + let _ = inner.counter.take(); + } return; } - let mut attrs: Vec = inner.selectors.on_response(response).into_iter().collect(); - attrs.append(&mut inner.attributes); + let attrs: Vec = inner.selectors.on_response(response).into_iter().collect(); + inner.attributes.extend(attrs); if let Some(selected_value) = inner .selector .as_ref() .and_then(|s| s.on_response(response)) { - inner.increment = Increment::Custom(selected_value.as_str().parse::().ok()) + let new_incr = match &inner.increment { + Increment::EventCustom(None) => { + Increment::Custom(selected_value.as_str().parse::().ok()) + } + Increment::Custom(None) => { + Increment::Custom(selected_value.as_str().parse::().ok()) + } + other => { + failfast_error!("this is a bug and should not happen, the increment should only be Custom or EventCustom, please open an issue: {other:?}"); + return; + } + }; + inner.increment = new_incr; } let increment = match inner.increment { @@ -1027,10 +1161,75 @@ where Some(incr) => incr as f64, None => 0f64, }, + Increment::EventUnit | Increment::EventDuration(_) | Increment::EventCustom(_) => { + // Nothing to do because we're incrementing on events + return; + } }; - if let Some(counter) = inner.counter.take() { - counter.add(increment, &attrs); + if increment != 0.0 { + if let Some(counter) = &inner.counter { + counter.add(increment, &inner.attributes); + } + inner.incremented = true; + } + } + + fn on_response_event(&self, response: &Self::EventResponse, ctx: &Context) { + let mut inner = self.inner.lock(); + if !inner.condition.evaluate_event_response(response, ctx) { + return; + } + let attrs: Vec = inner + .selectors + .on_response_event(response, ctx) + .into_iter() + .collect(); + inner.attributes.extend(attrs); + + if let Some(selected_value) = inner + .selector + .as_ref() + .and_then(|s| s.on_response_event(response, ctx)) + { + let new_incr = match &inner.increment { + Increment::EventCustom(None) => { + Increment::EventCustom(selected_value.as_str().parse::().ok()) + } + Increment::Custom(None) => { + Increment::EventCustom(selected_value.as_str().parse::().ok()) + } + other => { + failfast_error!("this is a bug and should not happen, the increment should only be Custom or EventCustom, please open an issue: {other:?}"); + return; + } + }; + inner.increment = new_incr; + } + + let increment = match &mut inner.increment { + Increment::EventUnit => 1f64, + Increment::EventDuration(instant) => { + let incr = instant.elapsed().as_secs_f64(); + // Set it to new instant for the next event + *instant = Instant::now(); + incr + } + Increment::Custom(val) | Increment::EventCustom(val) => { + let incr = match val { + Some(incr) => *incr as f64, + None => 0f64, + }; + // Set it to None again for the next event + *val = None; + incr + } + _ => 0f64, + }; + + inner.incremented = true; + if let Some(counter) = &inner.counter { + counter.add(increment, &inner.attributes); } } @@ -1040,9 +1239,11 @@ where attrs.append(&mut inner.attributes); let increment = match inner.increment { - Increment::Unit => 1f64, - Increment::Duration(instant) => instant.elapsed().as_secs_f64(), - Increment::Custom(val) => match val { + Increment::Unit | Increment::EventUnit => 1f64, + Increment::Duration(instant) | Increment::EventDuration(instant) => { + instant.elapsed().as_secs_f64() + } + Increment::Custom(val) | Increment::EventCustom(val) => match val { Some(incr) => incr as f64, None => 0f64, }, @@ -1063,11 +1264,16 @@ where // TODO add attribute error broken pipe ? cf https://github.com/apollographql/router/issues/4866 let inner = self.inner.try_lock(); if let Some(mut inner) = inner { + if inner.incremented { + return; + } if let Some(counter) = inner.counter.take() { let incr: f64 = match &inner.increment { - Increment::Unit => 1f64, - Increment::Duration(instant) => instant.elapsed().as_secs_f64(), - Increment::Custom(val) => match val { + Increment::Unit | Increment::EventUnit => 1f64, + Increment::Duration(instant) | Increment::EventDuration(instant) => { + instant.elapsed().as_secs_f64() + } + Increment::Custom(val) | Increment::EventCustom(val) => match val { Some(incr) => *incr as f64, None => 0f64, }, @@ -1091,6 +1297,7 @@ struct ActiveRequestsCounterInner { impl Instrumented for ActiveRequestsCounter { type Request = router::Request; type Response = router::Response; + type EventResponse = (); fn on_request(&self, request: &Self::Request) { let mut inner = self.inner.lock(); @@ -1180,15 +1387,19 @@ where pub(crate) selectors: Option>>, pub(crate) histogram: Option>, pub(crate) attributes: Vec, + // Useful when it's an histogram on events to know if we have to count for an event or not + pub(crate) updated: bool, } -impl Instrumented for CustomHistogram +impl Instrumented + for CustomHistogram where - A: Selectors + Default, - T: Selector, + A: Selectors + Default, + T: Selector, { type Request = Request; type Response = Response; + type EventResponse = EventResponse; fn on_request(&self, request: &Self::Request) { let mut inner = self.inner.lock(); @@ -1200,37 +1411,126 @@ where inner.attributes = selectors.on_request(request).into_iter().collect(); } if let Some(selected_value) = inner.selector.as_ref().and_then(|s| s.on_request(request)) { - inner.increment = Increment::Custom(selected_value.as_str().parse::().ok()) + let new_incr = match &inner.increment { + Increment::EventCustom(None) => { + Increment::EventCustom(selected_value.as_str().parse::().ok()) + } + Increment::Custom(None) => { + Increment::Custom(selected_value.as_str().parse::().ok()) + } + other => { + failfast_error!("this is a bug and should not happen, the increment should only be Custom or EventCustom, please open an issue: {other:?}"); + return; + } + }; + inner.increment = new_incr; } } fn on_response(&self, response: &Self::Response) { let mut inner = self.inner.lock(); if !inner.condition.evaluate_response(response) { - let _ = inner.histogram.take(); + if !matches!( + &inner.increment, + Increment::EventCustom(_) | Increment::EventDuration(_) | Increment::EventUnit + ) { + let _ = inner.histogram.take(); + } return; } - let mut attrs: Vec = inner + let attrs: Vec = inner .selectors .as_ref() .map(|s| s.on_response(response).into_iter().collect()) .unwrap_or_default(); - attrs.append(&mut inner.attributes); + inner.attributes.extend(attrs); if let Some(selected_value) = inner .selector .as_ref() .and_then(|s| s.on_response(response)) { - inner.increment = Increment::Custom(selected_value.as_str().parse::().ok()) + let new_incr = match &inner.increment { + Increment::EventCustom(None) => { + Increment::EventCustom(selected_value.as_str().parse::().ok()) + } + Increment::Custom(None) => { + Increment::Custom(selected_value.as_str().parse::().ok()) + } + other => { + failfast_error!("this is a bug and should not happen, the increment should only be Custom or EventCustom, please open an issue: {other:?}"); + return; + } + }; + inner.increment = new_incr; } let increment = match inner.increment { Increment::Unit => Some(1f64), Increment::Duration(instant) => Some(instant.elapsed().as_secs_f64()), Increment::Custom(val) => val.map(|incr| incr as f64), + Increment::EventUnit | Increment::EventDuration(_) | Increment::EventCustom(_) => { + // Nothing to do because we're incrementing on events + return; + } }; if let (Some(histogram), Some(increment)) = (inner.histogram.take(), increment) { + histogram.record(increment, &inner.attributes); + } + } + + fn on_response_event(&self, response: &Self::EventResponse, ctx: &Context) { + let mut inner = self.inner.lock(); + if !inner.condition.evaluate_event_response(response, ctx) { + return; + } + let mut attrs: Vec = inner + .selectors + .as_ref() + .map(|s| s.on_response_event(response, ctx).into_iter().collect()) + .unwrap_or_default(); + attrs.extend(inner.attributes.clone()); + if let Some(selected_value) = inner + .selector + .as_ref() + .and_then(|s| s.on_response_event(response, ctx)) + { + let new_incr = match &inner.increment { + Increment::EventCustom(None) => { + Increment::EventCustom(selected_value.as_str().parse::().ok()) + } + Increment::Custom(None) => { + Increment::EventCustom(selected_value.as_str().parse::().ok()) + } + other => { + failfast_error!("this is a bug and should not happen, the increment should only be Custom or EventCustom, please open an issue: {other:?}"); + return; + } + }; + inner.increment = new_incr; + } + + let increment: Option = match &mut inner.increment { + Increment::EventUnit => Some(1f64), + Increment::EventDuration(instant) => { + let incr = Some(instant.elapsed().as_secs_f64()); + // Need a new instant for the next event + *instant = Instant::now(); + incr + } + Increment::EventCustom(val) => { + let incr = val.map(|incr| incr as f64); + // Set it to None again + *val = None; + incr + } + Increment::Unit | Increment::Duration(_) | Increment::Custom(_) => { + // Nothing to do because we're incrementing on events + return; + } + }; + inner.updated = true; + if let (Some(histogram), Some(increment)) = (&inner.histogram, increment) { histogram.record(increment, &attrs); } } @@ -1245,9 +1545,11 @@ where attrs.append(&mut inner.attributes); let increment = match inner.increment { - Increment::Unit => Some(1f64), - Increment::Duration(instant) => Some(instant.elapsed().as_secs_f64()), - Increment::Custom(val) => val.map(|incr| incr as f64), + Increment::Unit | Increment::EventUnit => Some(1f64), + Increment::Duration(instant) | Increment::EventDuration(instant) => { + Some(instant.elapsed().as_secs_f64()) + } + Increment::Custom(val) | Increment::EventCustom(val) => val.map(|incr| incr as f64), }; if let (Some(histogram), Some(increment)) = (inner.histogram.take(), increment) { @@ -1265,11 +1567,18 @@ where // TODO add attribute error broken pipe ? cf https://github.com/apollographql/router/issues/4866 let inner = self.inner.try_lock(); if let Some(mut inner) = inner { + if inner.updated { + return; + } if let Some(histogram) = inner.histogram.take() { let increment = match &inner.increment { - Increment::Unit => Some(1f64), - Increment::Duration(instant) => Some(instant.elapsed().as_secs_f64()), - Increment::Custom(val) => val.map(|incr| incr as f64), + Increment::Unit | Increment::EventUnit => Some(1f64), + Increment::Duration(instant) | Increment::EventDuration(instant) => { + Some(instant.elapsed().as_secs_f64()) + } + Increment::Custom(val) | Increment::EventCustom(val) => { + val.map(|incr| incr as f64) + } }; if let Some(increment) = increment { @@ -1327,6 +1636,23 @@ mod tests { "http.response.status_code": true } }, + "acme.request.on_critical_error": { + "value": "unit", + "type": "counter", + "unit": "error", + "description": "my description", + "condition": { + "eq": [ + "request time out", + { + "error": "reason" + } + ] + }, + "attributes": { + "http.response.status_code": true + } + }, "acme.request.on_error_histo": { "value": "unit", "type": "histogram", @@ -1468,6 +1794,20 @@ mod tests { 2.0, "http.response.status_code" = 400 ); + + let router_instruments = config.new_router_instruments(); + let router_req = RouterRequest::fake_builder() + .header("content-length", "35") + .header("content-type", "application/graphql") + .build() + .unwrap(); + router_instruments.on_request(&router_req); + router_instruments.on_error(&BoxError::from("request time out"), &Context::new()); + assert_counter!( + "acme.request.on_critical_error", + 1.0, + "http.response.status_code" = 500 + ); } .with_metrics() .await; @@ -1495,6 +1835,38 @@ mod tests { } } }, + "acme.request.on_graphql_error": { + "value": "event_unit", + "type": "counter", + "unit": "error", + "description": "my description", + "condition": { + "eq": [ + "NOPE", + { + "response_errors": "$.[0].extensions.code" + } + ] + }, + "attributes": { + "response_errors": { + "response_errors": "$.*" + } + } + }, + "acme.request.on_graphql_data": { + "value": { + "response_data": "$.price" + }, + "type": "counter", + "unit": "$", + "description": "my description", + "attributes": { + "response.data": { + "response_data": "$.*" + } + } + }, "acme.query": { "value": "unit", "type": "counter", @@ -1547,9 +1919,27 @@ mod tests { .build() .unwrap(); custom_instruments.on_response(&supergraph_response); + custom_instruments.on_response_event( + &graphql::Response::builder() + .data(json!({ + "price": 500 + })) + .errors(vec![graphql::Error::builder() + .message("nope") + .extension_code("NOPE") + .build()]) + .build(), + &supergraph_req.context.clone(), + ); assert_counter!("acme.query", 1.0, query = "{me{name}}"); assert_counter!("acme.request.on_error", 1.0); + assert_counter!( + "acme.request.on_graphql_error", + 1.0, + response_errors = "{\"message\":\"nope\",\"extensions\":{\"code\":\"NOPE\"}}" + ); + assert_counter!("acme.request.on_graphql_data", 500.0, response.data = 500); let custom_instruments = SupergraphCustomInstruments::new(&config.supergraph.custom); let supergraph_req = supergraph::Request::fake_builder() @@ -1573,9 +1963,27 @@ mod tests { .build() .unwrap(); custom_instruments.on_response(&supergraph_response); + custom_instruments.on_response_event( + &graphql::Response::builder() + .data(json!({ + "price": 500 + })) + .errors(vec![graphql::Error::builder() + .message("nope") + .extension_code("NOPE") + .build()]) + .build(), + &supergraph_req.context.clone(), + ); assert_counter!("acme.query", 1.0, query = "{me{name}}"); assert_counter!("acme.request.on_error", 2.0); + assert_counter!( + "acme.request.on_graphql_error", + 2.0, + response_errors = "{\"message\":\"nope\",\"extensions\":{\"code\":\"NOPE\"}}" + ); + assert_counter!("acme.request.on_graphql_data", 1000.0, response.data = 500); let custom_instruments = SupergraphCustomInstruments::new(&config.supergraph.custom); let supergraph_req = supergraph::Request::fake_builder() @@ -1591,16 +1999,25 @@ mod tests { .status_code(StatusCode::OK) .header("content-type", "application/json") .header("content-length", "35") - .errors(vec![graphql::Error::builder() - .message("nope") - .extension_code("NOPE") - .build()]) + .data(serde_json_bytes::json!({"foo": "bar"})) .build() .unwrap(); custom_instruments.on_response(&supergraph_response); + custom_instruments.on_response_event( + &graphql::Response::builder() + .data(serde_json_bytes::json!({"foo": "bar"})) + .build(), + &supergraph_req.context.clone(), + ); assert_counter!("acme.query", 2.0, query = "{me{name}}"); assert_counter!("acme.request.on_error", 2.0); + assert_counter!( + "acme.request.on_graphql_error", + 2.0, + response_errors = "{\"message\":\"nope\",\"extensions\":{\"code\":\"NOPE\"}}" + ); + assert_counter!("acme.request.on_graphql_data", 1000.0, response.data = 500); } .with_metrics() .await; diff --git a/apollo-router/src/plugins/telemetry/config_new/mod.rs b/apollo-router/src/plugins/telemetry/config_new/mod.rs index b2979d9d45..3840336def 100644 --- a/apollo-router/src/plugins/telemetry/config_new/mod.rs +++ b/apollo-router/src/plugins/telemetry/config_new/mod.rs @@ -10,6 +10,7 @@ use super::otel::OpenTelemetrySpanExt; use super::otlp::TelemetryDataKind; use crate::plugins::telemetry::config::AttributeValue; use crate::plugins::telemetry::config_new::attributes::DefaultAttributeRequirementLevel; +use crate::Context; /// These modules contain a new config structure for telemetry that will progressively move to pub(crate) mod attributes; @@ -28,17 +29,31 @@ pub(crate) mod spans; pub(crate) trait Selectors { type Request; type Response; + type EventResponse; + fn on_request(&self, request: &Self::Request) -> Vec; fn on_response(&self, response: &Self::Response) -> Vec; + fn on_response_event(&self, _response: &Self::EventResponse, _ctx: &Context) -> Vec { + Vec::with_capacity(0) + } fn on_error(&self, error: &BoxError) -> Vec; } pub(crate) trait Selector { type Request; type Response; + type EventResponse; fn on_request(&self, request: &Self::Request) -> Option; fn on_response(&self, response: &Self::Response) -> Option; + fn on_response_event( + &self, + _response: &Self::EventResponse, + _ctx: &Context, + ) -> Option { + None + } + fn on_error(&self, error: &BoxError) -> Option; } pub(crate) trait DefaultForLevel { diff --git a/apollo-router/src/plugins/telemetry/config_new/selectors.rs b/apollo-router/src/plugins/telemetry/config_new/selectors.rs index 43af9d2ee7..ad9c9b5edd 100644 --- a/apollo-router/src/plugins/telemetry/config_new/selectors.rs +++ b/apollo-router/src/plugins/telemetry/config_new/selectors.rs @@ -23,6 +23,7 @@ use crate::plugins::telemetry::config_new::ToOtelValue; use crate::services::router; use crate::services::subgraph; use crate::services::supergraph; +use crate::Context; #[derive(Deserialize, JsonSchema, Clone, Debug)] #[cfg_attr(test, derive(PartialEq))] @@ -44,6 +45,17 @@ pub(crate) enum OperationName { Hash, } +#[allow(dead_code)] +#[derive(Deserialize, JsonSchema, Clone, Debug)] +#[cfg_attr(test, derive(PartialEq))] +#[serde(deny_unknown_fields, rename_all = "snake_case")] +pub(crate) enum ErrorRepr { + // /// The error code if available + // Code, + /// The error reason + Reason, +} + #[derive(Deserialize, JsonSchema, Clone, Debug)] #[cfg_attr(test, derive(PartialEq))] #[serde(deny_unknown_fields, rename_all = "snake_case")] @@ -153,11 +165,17 @@ pub(crate) enum RouterSelector { /// Boolean set to true if the response body contains graphql error on_graphql_error: bool, }, + Error { + #[allow(dead_code)] + /// Critical error if it happens + error: ErrorRepr, + }, } -#[derive(Deserialize, JsonSchema, Clone, Debug)] -#[cfg_attr(test, derive(PartialEq))] +#[derive(Deserialize, JsonSchema, Clone, Derivative)] +#[cfg_attr(test, derivative(PartialEq))] #[serde(deny_unknown_fields, untagged)] +#[derivative(Debug)] pub(crate) enum SupergraphSelector { OperationName { /// The operation name from the query. @@ -242,6 +260,32 @@ pub(crate) enum SupergraphSelector { /// Optional default value. default: Option, }, + ResponseData { + /// The supergraph response body json path of the chunks. + #[schemars(with = "String")] + #[derivative(Debug = "ignore", PartialEq = "ignore")] + #[serde(deserialize_with = "deserialize_jsonpath")] + response_data: JsonPathInst, + #[serde(skip)] + #[allow(dead_code)] + /// Optional redaction pattern. + redact: Option, + /// Optional default value. + default: Option, + }, + ResponseErrors { + /// The supergraph response body json path of the chunks. + #[schemars(with = "String")] + #[derivative(Debug = "ignore", PartialEq = "ignore")] + #[serde(deserialize_with = "deserialize_jsonpath")] + response_errors: JsonPathInst, + #[serde(skip)] + #[allow(dead_code)] + /// Optional redaction pattern. + redact: Option, + /// Optional default value. + default: Option, + }, Baggage { /// The name of the baggage item. baggage: String, @@ -267,6 +311,11 @@ pub(crate) enum SupergraphSelector { /// A static string value r#static: String, }, + Error { + #[allow(dead_code)] + /// Critical error if it happens + error: ErrorRepr, + }, /// Cost attributes #[allow(dead_code)] Cost { @@ -474,11 +523,17 @@ pub(crate) enum SubgraphSelector { /// A static string value r#static: String, }, + Error { + #[allow(dead_code)] + /// Critical error if it happens + error: ErrorRepr, + }, } impl Selector for RouterSelector { type Request = router::Request; type Response = router::Response; + type EventResponse = (); fn on_request(&self, request: &router::Request) -> Option { match self { @@ -566,11 +621,19 @@ impl Selector for RouterSelector { _ => None, } } + + fn on_error(&self, error: &tower::BoxError) -> Option { + match self { + RouterSelector::Error { .. } => Some(error.to_string().into()), + _ => None, + } + } } impl Selector for SupergraphSelector { type Request = supergraph::Request; type Response = supergraph::Response; + type EventResponse = crate::graphql::Response; fn on_request(&self, request: &supergraph::Request) -> Option { match self { @@ -688,8 +751,60 @@ impl Selector for SupergraphSelector { .and_then(|v| v.maybe_to_otel_value()) .or_else(|| default.maybe_to_otel_value()), SupergraphSelector::StaticField { r#static } => Some(r#static.clone().into()), + // For request + _ => None, + } + } + + fn on_response_event( + &self, + response: &Self::EventResponse, + ctx: &Context, + ) -> Option { + match self { + SupergraphSelector::ResponseData { + response_data, + default, + .. + } => if let Some(data) = &response.data { + let data: serde_json::Value = serde_json::to_value(data.clone()).ok()?; + let mut val = + JsonPathFinder::new(Box::new(data), Box::new(response_data.clone())).find(); + if let serde_json::Value::Array(array) = &mut val { + if array.len() == 1 { + val = array + .pop() + .expect("already checked the array had a length of 1; qed"); + } + } + + val.maybe_to_otel_value() + } else { + None + } + .or_else(|| default.maybe_to_otel_value()), + SupergraphSelector::ResponseErrors { + response_errors, + default, + .. + } => { + let errors = response.errors.clone(); + let data: serde_json::Value = serde_json::to_value(errors).ok()?; + let mut val = + JsonPathFinder::new(Box::new(data), Box::new(response_errors.clone())).find(); + if let serde_json::Value::Array(array) = &mut val { + if array.len() == 1 { + val = array + .pop() + .expect("already checked the array had a length of 1; qed"); + } + } + + val.maybe_to_otel_value() + } + .or_else(|| default.maybe_to_otel_value()), SupergraphSelector::Cost { cost } => { - let extensions = response.context.extensions().lock(); + let extensions = ctx.extensions().lock(); extensions .get::() .map(|cost_result| match cost { @@ -699,7 +814,13 @@ impl Selector for SupergraphSelector { CostValue::Result => cost_result.result.into(), }) } - // For request + _ => None, + } + } + + fn on_error(&self, error: &tower::BoxError) -> Option { + match self { + SupergraphSelector::Error { .. } => Some(error.to_string().into()), _ => None, } } @@ -708,6 +829,7 @@ impl Selector for SupergraphSelector { impl Selector for SubgraphSelector { type Request = subgraph::Request; type Response = subgraph::Response; + type EventResponse = (); fn on_request(&self, request: &subgraph::Request) -> Option { match self { @@ -941,6 +1063,13 @@ impl Selector for SubgraphSelector { _ => None, } } + + fn on_error(&self, error: &tower::BoxError) -> Option { + match self { + SubgraphSelector::Error { .. } => Some(error.to_string().into()), + _ => None, + } + } } #[cfg(test)] diff --git a/apollo-router/src/plugins/telemetry/dynamic_attribute.rs b/apollo-router/src/plugins/telemetry/dynamic_attribute.rs index 2d1a99ce75..fb8b801167 100644 --- a/apollo-router/src/plugins/telemetry/dynamic_attribute.rs +++ b/apollo-router/src/plugins/telemetry/dynamic_attribute.rs @@ -202,7 +202,7 @@ impl EventDynAttribute for ::tracing::Span { }, None => { // Can't use ::tracing::error! because it could create deadlock on extensions - eprintln!("no EventsAttributes, this is a bug"); + eprintln!("no OtelData, this is a bug"); } } } @@ -238,7 +238,7 @@ impl EventDynAttribute for ::tracing::Span { }, None => { // Can't use ::tracing::error! because it could create deadlock on extensions - eprintln!("no EventsAttributes, this is a bug"); + eprintln!("no OtelData, this is a bug"); } } } diff --git a/apollo-router/src/plugins/telemetry/fmt_layer.rs b/apollo-router/src/plugins/telemetry/fmt_layer.rs index 450f831b0b..b5c20175bf 100644 --- a/apollo-router/src/plugins/telemetry/fmt_layer.rs +++ b/apollo-router/src/plugins/telemetry/fmt_layer.rs @@ -446,7 +446,7 @@ subgraph: fmt::Subscriber::new().with(fmt_layer), generate_simple_span, ); - insta::assert_display_snapshot!(buff); + insta::assert_snapshot!(buff); } #[tokio::test] @@ -464,7 +464,7 @@ subgraph: generate_nested_spans, ); - insta::assert_display_snapshot!(buff.to_string()); + insta::assert_snapshot!(buff.to_string()); } #[tokio::test] @@ -481,7 +481,7 @@ subgraph: fmt::Subscriber::new().with(fmt_layer), generate_simple_span, ); - insta::assert_display_snapshot!(buff); + insta::assert_snapshot!(buff); } #[tokio::test] @@ -499,7 +499,7 @@ subgraph: generate_nested_spans, ); - insta::assert_display_snapshot!(buff.to_string()); + insta::assert_snapshot!(buff.to_string()); } #[tokio::test] @@ -523,7 +523,7 @@ subgraph: generate_nested_spans, ); - insta::assert_display_snapshot!(buff.to_string()); + insta::assert_snapshot!(buff.to_string()); } #[tokio::test] @@ -548,7 +548,7 @@ subgraph: generate_nested_spans, ); - insta::assert_display_snapshot!(buff.to_string()); + insta::assert_snapshot!(buff.to_string()); } #[tokio::test] @@ -593,7 +593,7 @@ subgraph: }, ); - insta::assert_display_snapshot!(buff.to_string()); + insta::assert_snapshot!(buff.to_string()); } #[tokio::test] @@ -640,7 +640,7 @@ subgraph: }, ); - insta::assert_display_snapshot!(buff.to_string()); + insta::assert_snapshot!(buff.to_string()); } #[tokio::test] @@ -772,7 +772,7 @@ subgraph: }, ); - insta::assert_display_snapshot!(buff.to_string()); + insta::assert_snapshot!(buff.to_string()); } #[tokio::test] @@ -905,7 +905,7 @@ subgraph: }, ); - insta::assert_display_snapshot!(buff.to_string()); + insta::assert_snapshot!(buff.to_string()); } // TODO add test using on_request/on_reponse/on_error diff --git a/apollo-router/src/plugins/telemetry/metrics/apollo/snapshots/apollo_router__plugins__telemetry__metrics__apollo__studio__test__aggregation.snap b/apollo-router/src/plugins/telemetry/metrics/apollo/snapshots/apollo_router__plugins__telemetry__metrics__apollo__studio__test__aggregation.snap index 533f05f715..248fe9ea91 100644 --- a/apollo-router/src/plugins/telemetry/metrics/apollo/snapshots/apollo_router__plugins__telemetry__metrics__apollo__studio__test__aggregation.snap +++ b/apollo-router/src/plugins/telemetry/metrics/apollo/snapshots/apollo_router__plugins__telemetry__metrics__apollo__studio__test__aggregation.snap @@ -12,14 +12,16 @@ expression: aggregated_metrics "client_name": "client_1", "client_version": "version_1", "operation_type": "", - "operation_subtype": "" + "operation_subtype": "", + "result": "" }, { "context": { "client_name": "client_1", "client_version": "version_1", "operation_type": "", - "operation_subtype": "" + "operation_subtype": "", + "result": "" }, "query_latency_stats": { "request_latencies": { diff --git a/apollo-router/src/plugins/telemetry/metrics/apollo/studio.rs b/apollo-router/src/plugins/telemetry/metrics/apollo/studio.rs index d7a85a5cd8..12f79bab2d 100644 --- a/apollo-router/src/plugins/telemetry/metrics/apollo/studio.rs +++ b/apollo-router/src/plugins/telemetry/metrics/apollo/studio.rs @@ -194,6 +194,9 @@ impl From .collect(), query_latency_stats: Some(stats.query_latency_stats.into()), context: Some(stats.context), + limits_stats: None, + local_per_type_stat: HashMap::new(), + operation_count: 0, } } } @@ -329,6 +332,7 @@ mod test { SingleStats { stats_with_context: SingleContextualizedStats { context: StatsContext { + result: "".to_string(), client_name: client_name.to_string(), client_version: client_version.to_string(), operation_type: String::new(), diff --git a/apollo-router/src/plugins/telemetry/metrics/snapshots/apollo_router__plugins__telemetry__metrics__apollo__test__apollo_metrics_for_subscription.snap b/apollo-router/src/plugins/telemetry/metrics/snapshots/apollo_router__plugins__telemetry__metrics__apollo__test__apollo_metrics_for_subscription.snap index c50e070b89..230edb3b97 100644 --- a/apollo-router/src/plugins/telemetry/metrics/snapshots/apollo_router__plugins__telemetry__metrics__apollo__test__apollo_metrics_for_subscription.snap +++ b/apollo-router/src/plugins/telemetry/metrics/snapshots/apollo_router__plugins__telemetry__metrics__apollo__test__apollo_metrics_for_subscription.snap @@ -12,7 +12,8 @@ expression: results "client_name": "test_client", "client_version": "1.0-test", "operation_type": "subscription", - "operation_subtype": "subscription-request" + "operation_subtype": "subscription-request", + "result": "" }, "query_latency_stats": { "latency": { diff --git a/apollo-router/src/plugins/telemetry/metrics/snapshots/apollo_router__plugins__telemetry__metrics__apollo__test__apollo_metrics_for_subscription_error.snap b/apollo-router/src/plugins/telemetry/metrics/snapshots/apollo_router__plugins__telemetry__metrics__apollo__test__apollo_metrics_for_subscription_error.snap index a89e16adb0..3d3799810c 100644 --- a/apollo-router/src/plugins/telemetry/metrics/snapshots/apollo_router__plugins__telemetry__metrics__apollo__test__apollo_metrics_for_subscription_error.snap +++ b/apollo-router/src/plugins/telemetry/metrics/snapshots/apollo_router__plugins__telemetry__metrics__apollo__test__apollo_metrics_for_subscription_error.snap @@ -12,7 +12,8 @@ expression: results "client_name": "test_client", "client_version": "1.0-test", "operation_type": "subscription", - "operation_subtype": "subscription-request" + "operation_subtype": "subscription-request", + "result": "" }, "query_latency_stats": { "latency": { diff --git a/apollo-router/src/plugins/telemetry/metrics/snapshots/apollo_router__plugins__telemetry__metrics__apollo__test__apollo_metrics_single_operation.snap b/apollo-router/src/plugins/telemetry/metrics/snapshots/apollo_router__plugins__telemetry__metrics__apollo__test__apollo_metrics_single_operation.snap index d6c90bfc87..b1b900b77d 100644 --- a/apollo-router/src/plugins/telemetry/metrics/snapshots/apollo_router__plugins__telemetry__metrics__apollo__test__apollo_metrics_single_operation.snap +++ b/apollo-router/src/plugins/telemetry/metrics/snapshots/apollo_router__plugins__telemetry__metrics__apollo__test__apollo_metrics_single_operation.snap @@ -12,7 +12,8 @@ expression: results "client_name": "test_client", "client_version": "1.0-test", "operation_type": "query", - "operation_subtype": "" + "operation_subtype": "", + "result": "" }, "query_latency_stats": { "latency": { diff --git a/apollo-router/src/plugins/telemetry/metrics/snapshots/apollo_router__plugins__telemetry__metrics__apollo__test__apollo_metrics_unknown_operation.snap b/apollo-router/src/plugins/telemetry/metrics/snapshots/apollo_router__plugins__telemetry__metrics__apollo__test__apollo_metrics_unknown_operation.snap index 4de35b4c9f..4dddadab0d 100644 --- a/apollo-router/src/plugins/telemetry/metrics/snapshots/apollo_router__plugins__telemetry__metrics__apollo__test__apollo_metrics_unknown_operation.snap +++ b/apollo-router/src/plugins/telemetry/metrics/snapshots/apollo_router__plugins__telemetry__metrics__apollo__test__apollo_metrics_unknown_operation.snap @@ -12,7 +12,8 @@ expression: results "client_name": "test_client", "client_version": "1.0-test", "operation_type": "query", - "operation_subtype": "" + "operation_subtype": "", + "result": "" }, "query_latency_stats": { "latency": { diff --git a/apollo-router/src/plugins/telemetry/mod.rs b/apollo-router/src/plugins/telemetry/mod.rs index 5d9a3a6007..4c2b884c01 100644 --- a/apollo-router/src/plugins/telemetry/mod.rs +++ b/apollo-router/src/plugins/telemetry/mod.rs @@ -610,6 +610,8 @@ impl Plugin for Telemetry { ctx.clone(), result, start.elapsed(), + custom_instruments, + supergraph_events, ) .await; Self::update_metrics_on_response_events( @@ -921,6 +923,8 @@ impl Telemetry { context: Context, result: Result, request_duration: Duration, + custom_instruments: SupergraphInstruments, + custom_events: SupergraphEvents, ) -> Result { let mut metric_attrs = { context @@ -944,8 +948,13 @@ impl Telemetry { response.response.status().as_u16().to_string(), )); + let ctx = context.clone(); // Wait for the first response of the stream let (parts, stream) = response.response.into_parts(); + let stream = stream.inspect(move |resp| { + custom_instruments.on_response_event(resp, &ctx); + custom_events.on_response_event(resp, &ctx); + }); let (first_response, rest) = stream.into_future().await; let attributes = config @@ -1381,6 +1390,7 @@ impl Telemetry { SingleStats { stats_with_context: SingleContextualizedStats { context: StatsContext { + result: "".to_string(), client_name: context .get(CLIENT_NAME) .unwrap_or_default() @@ -1844,9 +1854,10 @@ impl CustomTraceIdPropagator { fn extract_span_context(&self, extractor: &dyn Extractor) -> Option { let trace_id = extractor.get(&self.header_name)?; + let trace_id = trace_id.replace('-', ""); // extract trace id - let trace_id = match opentelemetry::trace::TraceId::from_hex(trace_id) { + let trace_id = match opentelemetry::trace::TraceId::from_hex(&trace_id) { Ok(trace_id) => trace_id, Err(err) => { ::tracing::error!("cannot generate custom trace_id: {err}"); @@ -1903,6 +1914,7 @@ struct EnableSubgraphFtv1; // #[cfg(test)] mod tests { + use std::collections::HashMap; use std::fmt::Debug; use std::ops::DerefMut; use std::sync::Arc; @@ -1933,6 +1945,7 @@ mod tests { use tracing_subscriber::Layer; use super::apollo::ForwardHeaders; + use super::CustomTraceIdPropagator; use super::Telemetry; use crate::error::FetchError; use crate::graphql; @@ -2958,4 +2971,18 @@ mod tests { .await; test_layer.assert_log_entry_count("other error", 2); } + + #[tokio::test] + async fn test_custom_trace_id_propagator_strip_dashes_in_trace_id() { + let header = String::from("x-trace-id"); + let trace_id = String::from("04f9e396-465c-4840-bc2b-f493b8b1a7fc"); + let expected_trace_id = String::from("04f9e396465c4840bc2bf493b8b1a7fc"); + + let propagator = CustomTraceIdPropagator::new(header.clone()); + let mut headers: HashMap = HashMap::new(); + headers.insert(header, trace_id); + let span = propagator.extract_span_context(&headers); + assert!(span.is_some()); + assert_eq!(span.unwrap().trace_id().to_string(), expected_trace_id); + } } diff --git a/apollo-router/src/plugins/telemetry/otel/mod.rs b/apollo-router/src/plugins/telemetry/otel/mod.rs index 85ec219012..81c378bdbc 100644 --- a/apollo-router/src/plugins/telemetry/otel/mod.rs +++ b/apollo-router/src/plugins/telemetry/otel/mod.rs @@ -16,7 +16,7 @@ pub(crate) use tracer::PreSampledTracer; /// Per-span OpenTelemetry data tracked by this crate. /// /// Useful for implementing [PreSampledTracer] in alternate otel SDKs. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub(crate) struct OtelData { /// The parent otel `Context` for the current tracing span. pub(crate) parent_cx: opentelemetry::Context, diff --git a/apollo-router/src/plugins/telemetry/proto/reports.proto b/apollo-router/src/plugins/telemetry/proto/reports.proto index 8e24ac18e0..0f03a7900e 100644 --- a/apollo-router/src/plugins/telemetry/proto/reports.proto +++ b/apollo-router/src/plugins/telemetry/proto/reports.proto @@ -6,6 +6,8 @@ syntax = "proto3"; import "google/protobuf/timestamp.proto"; + + message Trace { message CachePolicy { enum Scope { @@ -196,6 +198,26 @@ message Trace { } } + // The cost of the operation + message Limits { + // The result of the operation. + string result = 1; + // The strategy used in cost calculations. + string strategy = 2; + // The estimated cost as calculated via the strategy specified in strategy + uint64 cost_estimated = 3; + // The actual cost using the strategy specified in strategy + uint64 cost_actual = 4; + // The depth of the query + uint64 depth = 5; + // The height of the query + uint64 height = 6; + // The number of aliases in the query + uint64 alias_count = 7; + // The number of root fields in the query + uint64 root_field_count = 8; + } + // Wallclock time when the trace began. google.protobuf.Timestamp start_time = 4; // required // Wallclock time when the trace ended. @@ -283,6 +305,9 @@ message Trace { // 0 is treated as 1 for backwards compatibility. double field_execution_weight = 31; + // The limits information of the query. + Limits limits = 32; + // removed: Node parse = 12; Node validate = 13; // Id128 server_id = 1; Id128 client_id = 2; @@ -369,6 +394,29 @@ message QueryLatencyStats { reserved 1, 6, 9, 10; } +// Stats on the query that can be populated by the gateway or router. +message LimitsStats { + // The strategy used in cost calculations. + string strategy = 1; + // The estimated cost as calculated via the strategy specified in stats context + // The reason that this is a histogram rather than fixed cost is that it can be affected by paging variables. + repeated sint64 cost_estimated = 2 [(js_use_toArray) = true]; + // The maximum estimated cost of the query + uint64 max_cost_estimated = 3; + // The actual cost using the strategy specified in stats context + repeated sint64 cost_actual = 4 [(js_use_toArray) = true]; + // The maximum estimated cost of the query + uint64 max_cost_actual = 5; + // The total depth of the query + uint64 depth = 6; + // The height of the query + uint64 height = 7; + // The number of aliases in the query + uint64 alias_count = 8; + // The number of root fields in the query + uint64 root_field_count = 9; +} + // The context around a block of stats and traces indicating from which client the operation was executed and its // operation type. Operation type and subtype are only used by Apollo Router. message StatsContext { @@ -377,6 +425,9 @@ message StatsContext { string client_version = 3; string operation_type = 4; string operation_subtype = 5; + // The result of the operation. Either OK or the error code that caused the operation to fail. + // This will not contain all errors from a query, only the primary reason the operation failed. e.g. a limits failure or an auth failure. + string result = 6; } message ContextualizedQueryLatencyStats { @@ -424,12 +475,27 @@ message FieldStat { reserved 1, 2, 7, 8; } +// As FieldStat only gets returned for FTV1 payloads this is a separate message that can be used to collect stats in the router or gateway obtained directly from the request schema and response. +message LocalFieldStat { + string return_type = 1; // required; eg "String!" for User.email:String! + // Histogram of returned array sizes + repeated sint64 array_size = 2 [(js_use_toArray) = true]; +} + message TypeStat { // Key is (eg) "email" for User.email:String! map per_field_stat = 3; + reserved 1, 2; } +message LocalTypeStat { + // Key is (eg) "email" for User.email:String! + // Unlike FieldStat, this is populated outside of FTV1 requests. + map local_per_field_stat = 1; +} + + message ReferencedFieldsForType { // Contains (eg) "email" for User.email:String! repeated string field_names = 1; @@ -482,6 +548,7 @@ message Report { bool traces_pre_aggregated = 7; } + message ContextualizedStats { StatsContext context = 1; QueryLatencyStats query_latency_stats = 2; @@ -489,6 +556,16 @@ message ContextualizedStats { // field executions and thus only reflects operations for which field-level tracing occurred. map per_type_stat = 3; + // Per type stats that are obtained directly by the router or gateway rather than FTV1. + map local_per_type_stat = 7; + + // Stats that contain limits information for the query. + LimitsStats limits_stats = 8; + + // Total number of operations processed during this period for this context. This includes all operations, even if they are sampled + // and not included in the query latency stats. + uint64 operation_count = 9; + reserved 4, 5; } @@ -523,3 +600,4 @@ message TracesAndStats { reserved 3; } + diff --git a/apollo-router/src/plugins/telemetry/reload.rs b/apollo-router/src/plugins/telemetry/reload.rs index 350dce62d5..bdf182a4aa 100644 --- a/apollo-router/src/plugins/telemetry/reload.rs +++ b/apollo-router/src/plugins/telemetry/reload.rs @@ -46,6 +46,7 @@ use crate::plugins::telemetry::otel; use crate::plugins::telemetry::otel::OpenTelemetryLayer; use crate::plugins::telemetry::otel::PreSampledTracer; use crate::plugins::telemetry::tracing::reload::ReloadTracer; +use crate::query_planner::subscription::SUBSCRIPTION_EVENT_SPAN_NAME; use crate::router_factory::STARTING_SPAN_NAME; pub(crate) type LayeredRegistry = @@ -237,7 +238,10 @@ where // we only make the sampling decision on the root span. If we reach here for any other span, // it means that the parent span was not enabled, so we should not enable this span either - if meta.name() != REQUEST_SPAN_NAME && meta.name() != ROUTER_SPAN_NAME { + if meta.name() != REQUEST_SPAN_NAME + && meta.name() != ROUTER_SPAN_NAME + && meta.name() != SUBSCRIPTION_EVENT_SPAN_NAME + { return false; } diff --git a/apollo-router/src/plugins/telemetry/testdata/custom_events.router.yaml b/apollo-router/src/plugins/telemetry/testdata/custom_events.router.yaml index a8c0efa3fb..e27f8ab43e 100644 --- a/apollo-router/src/plugins/telemetry/testdata/custom_events.router.yaml +++ b/apollo-router/src/plugins/telemetry/testdata/custom_events.router.yaml @@ -11,6 +11,17 @@ telemetry: error: info # Custom events + my.disabled_request_event: + message: "my event message" + level: off + on: request + attributes: + http.request.body.size: true + # Only log when the x-log-request header is `log` + condition: + eq: + - "log" + - request_header: "x-log-request" my.request_event: message: "my event message" level: info @@ -40,6 +51,15 @@ telemetry: error: info # Custom events + my.disabled_request.event: + message: "my event message" + level: off + on: request + # Only log when the x-log-request header is `log` + condition: + eq: + - "log" + - request_header: "x-log-request" my.request.event: message: "my event message" level: info @@ -64,6 +84,10 @@ telemetry: error: error # Custom events + my.disabled_request.event: + message: "my event message" + level: off + on: request my.request.event: message: "my event message" level: info diff --git a/apollo-router/src/plugins/test.rs b/apollo-router/src/plugins/test.rs index 92b5e6b14a..c31ae9acc7 100644 --- a/apollo-router/src/plugins/test.rs +++ b/apollo-router/src/plugins/test.rs @@ -88,7 +88,7 @@ impl PluginTestHarness { .unwrap_or(Value::Object(Default::default())); let (supergraph_sdl, parsed_schema, subgraph_schemas) = if let Some(schema) = schema { - let planner = BridgeQueryPlanner::new(schema.to_string(), Arc::new(config)) + let planner = BridgeQueryPlanner::new(schema.to_string(), Arc::new(config), None) .await .unwrap(); ( diff --git a/apollo-router/src/query_planner/bridge_query_planner.rs b/apollo-router/src/query_planner/bridge_query_planner.rs index 29de239fab..9d3b3bcec9 100644 --- a/apollo-router/src/query_planner/bridge_query_planner.rs +++ b/apollo-router/src/query_planner/bridge_query_planner.rs @@ -6,19 +6,18 @@ use std::fmt::Write; use std::sync::Arc; use apollo_compiler::ast; +use apollo_compiler::ast::Name; use apollo_compiler::validation::Valid; use apollo_compiler::ExecutableDocument; +use apollo_federation::error::FederationError; use apollo_federation::query_plan::query_planner::QueryPlanner; use futures::future::BoxFuture; use opentelemetry_api::metrics::MeterProvider as _; use opentelemetry_api::metrics::ObservableGauge; use opentelemetry_api::KeyValue; -use router_bridge::planner::IncrementalDeliverySupport; use router_bridge::planner::PlanOptions; use router_bridge::planner::PlanSuccess; use router_bridge::planner::Planner; -use router_bridge::planner::QueryPlannerConfig; -use router_bridge::planner::QueryPlannerDebugConfig; use router_bridge::planner::UsageReporting; use serde::Deserialize; use serde_json_bytes::Map; @@ -108,37 +107,23 @@ impl PlannerMode { async fn new( schema: &Schema, configuration: &Configuration, + old_planner: Option>>, ) -> Result { Ok(match configuration.experimental_query_planner_mode { QueryPlannerMode::New => Self::Rust { js_for_api_schema_and_introspection_and_operation_signature: Self::js( &schema.raw_sdl, configuration, + old_planner, ) .await?, rust: Self::rust(schema, configuration)?, }, - QueryPlannerMode::Legacy => Self::Js(Self::js(&schema.raw_sdl, configuration).await?), - QueryPlannerMode::Both => Self::Both { - js: Self::js(&schema.raw_sdl, configuration).await?, - rust: Self::rust(schema, configuration)?, - }, - }) - } - - fn from_js( - js: Arc>, - schema: &Schema, - configuration: &Configuration, - ) -> Result { - Ok(match configuration.experimental_query_planner_mode { - QueryPlannerMode::New => Self::Rust { - js_for_api_schema_and_introspection_and_operation_signature: js, - rust: Self::rust(schema, configuration)?, - }, - QueryPlannerMode::Legacy => Self::Js(js), + QueryPlannerMode::Legacy => { + Self::Js(Self::js(&schema.raw_sdl, configuration, old_planner).await?) + } QueryPlannerMode::Both => Self::Both { - js, + js: Self::js(&schema.raw_sdl, configuration, old_planner).await?, rust: Self::rust(schema, configuration)?, }, }) @@ -169,32 +154,17 @@ impl PlannerMode { async fn js( sdl: &str, configuration: &Configuration, + old_planner: Option>>, ) -> Result>, ServiceBuildError> { - let planner = Planner::new( - sdl.to_owned(), - QueryPlannerConfig { - reuse_query_fragments: configuration.supergraph.reuse_query_fragments, - generate_query_fragments: Some(configuration.supergraph.generate_query_fragments), - incremental_delivery: Some(IncrementalDeliverySupport { - enable_defer: Some(configuration.supergraph.defer_support), - }), - graphql_validation: false, - debug: Some(QueryPlannerDebugConfig { - bypass_planner_for_single_subgraph: None, - max_evaluated_plans: configuration - .supergraph - .query_planning - .experimental_plans_limit - .or(Some(10000)), - paths_limit: configuration - .supergraph - .query_planning - .experimental_paths_limit, - }), - type_conditioned_fetching: configuration.experimental_type_conditioned_fetching, - }, - ) - .await?; + let query_planner_configuration = configuration.js_query_planner_config(); + let planner = match old_planner { + None => Planner::new(sdl.to_owned(), query_planner_configuration).await?, + Some(old_planner) => { + old_planner + .update(sdl.to_owned(), query_planner_configuration) + .await? + } + }; Ok(Arc::new(planner)) } @@ -234,8 +204,11 @@ impl PlannerMode { ) .map_err(|e| QueryPlannerError::OperationValidationErrors(e.errors.into()))?; - let plan = rust - .build_query_plan(&document, operation.as_deref()) + let plan = operation + .as_deref() + .map(|n| Name::new(n).map_err(FederationError::from)) + .transpose() + .and_then(|operation| rust.build_query_plan(&document, operation)) .map_err(|e| QueryPlannerError::FederationError(e.to_string()))?; // Dummy value overwritten below in `BrigeQueryPlanner::plan` @@ -267,7 +240,8 @@ impl PlannerMode { // remove `USING_CATCH_UNWIND` and this use of `catch_unwind`. let rust_result = std::panic::catch_unwind(|| { USING_CATCH_UNWIND.set(true); - let result = rust.build_query_plan(&document, operation.as_deref()); + let operation = operation.as_deref().map(Name::new).transpose()?; + let result = rust.build_query_plan(&document, operation); USING_CATCH_UNWIND.set(false); result }) @@ -312,8 +286,16 @@ impl PlannerMode { (Ok(js_plan), Ok(rust_plan)) => { is_matched = js_plan.data.query_plan == rust_plan.into(); - if !is_matched { - // TODO: tracing::debug!(diff) + if is_matched { + tracing::debug!("JS and Rust query plans match! 🎉"); + } else { + tracing::warn!("JS v.s. Rust query plan mismatch"); + if let Some(formatted) = &js_plan.data.formatted_query_plan { + tracing::debug!( + "Diff:\n{}", + render_diff(&diff::lines(formatted, &rust_plan.to_string())) + ); + } } } } @@ -360,11 +342,12 @@ impl PlannerMode { impl BridgeQueryPlanner { pub(crate) async fn new( - sdl: String, + schema: String, configuration: Arc, + old_planner: Option>>, ) -> Result { - let schema = Schema::parse(&sdl, &configuration)?; - let planner = PlannerMode::new(&schema, &configuration).await?; + let schema = Schema::parse(&schema, &configuration)?; + let planner = PlannerMode::new(&schema, &configuration, old_planner).await?; let api_schema_string = match configuration.experimental_api_schema_generation_mode { crate::configuration::ApiSchemaMode::Legacy => { @@ -434,6 +417,7 @@ impl BridgeQueryPlanner { js_result? } }; + let api_schema = Schema::parse_compiler_schema(&api_schema_string)?; let schema = Arc::new(schema.with_api_schema(api_schema)); @@ -467,77 +451,6 @@ impl BridgeQueryPlanner { }) } - pub(crate) async fn new_from_planner( - old_planner: Arc>, - schema: String, - configuration: Arc, - ) -> Result { - let planner = Arc::new( - old_planner - .update( - schema.clone(), - QueryPlannerConfig { - incremental_delivery: Some(IncrementalDeliverySupport { - enable_defer: Some(configuration.supergraph.defer_support), - }), - graphql_validation: false, - reuse_query_fragments: configuration.supergraph.reuse_query_fragments, - generate_query_fragments: Some( - configuration.supergraph.generate_query_fragments, - ), - debug: Some(QueryPlannerDebugConfig { - bypass_planner_for_single_subgraph: None, - max_evaluated_plans: configuration - .supergraph - .query_planning - .experimental_plans_limit - .or(Some(10000)), - paths_limit: configuration - .supergraph - .query_planning - .experimental_paths_limit, - }), - type_conditioned_fetching: configuration - .experimental_type_conditioned_fetching, - }, - ) - .await?, - ); - - let api_schema = planner.api_schema().await?; - let api_schema = Schema::parse_compiler_schema(&api_schema.schema)?; - let schema = Arc::new(Schema::parse(&schema, &configuration)?.with_api_schema(api_schema)); - - let mut subgraph_schemas: HashMap>> = - HashMap::new(); - for (name, schema_str) in planner.subgraphs().await? { - let schema = apollo_compiler::Schema::parse_and_validate(schema_str, "") - .map_err(|errors| SchemaError::Validate(errors.into()))?; - subgraph_schemas.insert(name, Arc::new(schema)); - } - let subgraph_schemas = Arc::new(subgraph_schemas); - - let introspection = if configuration.supergraph.introspection { - Some(Arc::new(Introspection::new(planner.clone()).await?)) - } else { - None - }; - - let enable_authorization_directives = - AuthorizationPlugin::enable_directives(&configuration, &schema)?; - let federation_instrument = federation_version_instrument(schema.federation_version()); - let planner = PlannerMode::from_js(planner, &schema, &configuration)?; - Ok(Self { - planner, - schema, - subgraph_schemas, - introspection, - enable_authorization_directives, - configuration, - _federation_instrument: federation_instrument, - }) - } - pub(crate) fn planner(&self) -> Arc> { self.planner .js_for_api_schema_and_introspection_and_operation_signature() @@ -1292,6 +1205,7 @@ mod tests { let _planner = BridgeQueryPlanner::new( include_str!("../testdata/minimal_supergraph.graphql").into(), Default::default(), + None, ) .await .unwrap(); @@ -1309,6 +1223,7 @@ mod tests { let _planner = BridgeQueryPlanner::new( include_str!("../testdata/minimal_fed2_supergraph.graphql").into(), Default::default(), + None, ) .await .unwrap(); @@ -1328,7 +1243,7 @@ mod tests { let schema = Schema::parse_test(EXAMPLE_SCHEMA, &Default::default()).unwrap(); let query = include_str!("testdata/unknown_introspection_query.graphql"); - let planner = BridgeQueryPlanner::new(EXAMPLE_SCHEMA.to_string(), Default::default()) + let planner = BridgeQueryPlanner::new(EXAMPLE_SCHEMA.to_string(), Default::default(), None) .await .unwrap(); @@ -1707,7 +1622,7 @@ mod tests { configuration.supergraph.introspection = true; let configuration = Arc::new(configuration); - let planner = BridgeQueryPlanner::new(schema.to_string(), configuration.clone()) + let planner = BridgeQueryPlanner::new(schema.to_string(), configuration.clone(), None) .await .unwrap(); diff --git a/apollo-router/src/query_planner/bridge_query_planner_pool.rs b/apollo-router/src/query_planner/bridge_query_planner_pool.rs index 4b7dd47fff..b67d8ce2ee 100644 --- a/apollo-router/src/query_planner/bridge_query_planner_pool.rs +++ b/apollo-router/src/query_planner/bridge_query_planner_pool.rs @@ -66,13 +66,10 @@ impl BridgeQueryPlannerPool { let sdl = schema.clone(); let configuration = configuration.clone(); - if let Some(old_planner) = old_planners_iterator.next() { - join_set.spawn(async move { - BridgeQueryPlanner::new_from_planner(old_planner, sdl, configuration).await - }); - } else { - join_set.spawn(async move { BridgeQueryPlanner::new(sdl, configuration).await }); - } + let old_planner = old_planners_iterator.next(); + join_set.spawn(async move { + BridgeQueryPlanner::new(sdl, configuration, old_planner).await + }); }); let mut bridge_query_planners = Vec::new(); diff --git a/apollo-router/src/query_planner/caching_query_planner.rs b/apollo-router/src/query_planner/caching_query_planner.rs index 2772e87db0..a6b7594ab2 100644 --- a/apollo-router/src/query_planner/caching_query_planner.rs +++ b/apollo-router/src/query_planner/caching_query_planner.rs @@ -12,7 +12,9 @@ use rand::seq::SliceRandom; use rand::thread_rng; use router_bridge::planner::PlanOptions; use router_bridge::planner::Planner; +use router_bridge::planner::QueryPlannerConfig; use router_bridge::planner::UsageReporting; +use serde::Serialize; use sha2::Digest; use sha2::Sha256; use tower::BoxError; @@ -50,6 +52,15 @@ pub(crate) type Plugins = IndexMap>; pub(crate) type InMemoryCachePlanner = InMemoryCache>>; +#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)] +pub(crate) enum ConfigMode { + //FIXME: add the Rust planner structure once it is hashable and serializable, + // for now use the JS config as it expected to be identical to the Rust one + Rust(Arc), + Both(Arc), + Js(Arc), +} + /// A query planner wrapper that caches results. /// /// The query planner performs LRU caching. @@ -62,6 +73,8 @@ pub(crate) struct CachingQueryPlanner { schema: Arc, plugins: Arc, enable_authorization_directives: bool, + config_mode: ConfigMode, + introspection: bool, } impl CachingQueryPlanner @@ -90,12 +103,26 @@ where let enable_authorization_directives = AuthorizationPlugin::enable_directives(configuration, &schema).unwrap_or(false); + + let config_mode = match configuration.experimental_query_planner_mode { + crate::configuration::QueryPlannerMode::New => { + ConfigMode::Rust(Arc::new(configuration.js_query_planner_config())) + } + crate::configuration::QueryPlannerMode::Legacy => { + ConfigMode::Js(Arc::new(configuration.js_query_planner_config())) + } + crate::configuration::QueryPlannerMode::Both => { + ConfigMode::Both(Arc::new(configuration.js_query_planner_config())) + } + }; Ok(Self { cache, delegate, schema, plugins: Arc::new(plugins), enable_authorization_directives, + config_mode, + introspection: configuration.supergraph.introspection, }) } @@ -141,7 +168,9 @@ where hash, metadata, plan_options, - .. + config_mode: _, + sdl: _, + introspection: _, }, _, )| WarmUpCachingQueryKey { @@ -150,6 +179,8 @@ where hash: Some(hash.clone()), metadata: metadata.clone(), plan_options: plan_options.clone(), + config_mode: self.config_mode.clone(), + introspection: self.introspection, }, ) .take(count) @@ -181,6 +212,8 @@ where hash: None, metadata: CacheKeyMetadata::default(), plan_options: PlanOptions::default(), + config_mode: self.config_mode.clone(), + introspection: self.introspection, }); } } @@ -195,6 +228,8 @@ where hash, metadata, plan_options, + config_mode: _, + introspection: _, } in all_cache_keys { let context = Context::new(); @@ -210,6 +245,8 @@ where sdl: Arc::clone(&self.schema.raw_sdl), metadata, plan_options, + config_mode: self.config_mode.clone(), + introspection: self.introspection, }; if experimental_reuse_query_plans { @@ -391,6 +428,8 @@ where sdl: Arc::clone(&self.schema.raw_sdl), metadata, plan_options, + config_mode: self.config_mode.clone(), + introspection: self.introspection, }; let context = request.context.clone(); @@ -530,8 +569,13 @@ pub(crate) struct CachingQueryKey { pub(crate) hash: Arc, pub(crate) metadata: CacheKeyMetadata, pub(crate) plan_options: PlanOptions, + pub(crate) config_mode: ConfigMode, + pub(crate) introspection: bool, } +// Update this key every time the cache key or the query plan format has to change. +// When changed it MUST BE CALLED OUT PROMINENTLY IN THE CHANGELOG. +const CACHE_KEY_VERSION: usize = 0; const FEDERATION_VERSION: &str = std::env!("FEDERATION_VERSION"); impl std::fmt::Display for CachingQueryKey { @@ -545,23 +589,29 @@ impl std::fmt::Display for CachingQueryKey { hasher.update( &serde_json::to_vec(&self.plan_options).expect("serialization should not fail"), ); + hasher + .update(&serde_json::to_vec(&self.config_mode).expect("serialization should not fail")); hasher.update(&serde_json::to_vec(&self.sdl).expect("serialization should not fail")); + hasher.update([self.introspection as u8]); let metadata = hex::encode(hasher.finalize()); write!( f, - "plan:{}:{}:{}:{}", - FEDERATION_VERSION, self.hash, operation, metadata, + "plan:{}:{}:{}:{}:{}", + CACHE_KEY_VERSION, FEDERATION_VERSION, self.hash, operation, metadata, ) } } impl Hash for CachingQueryKey { fn hash(&self, state: &mut H) { + self.sdl.hash(state); self.hash.0.hash(state); self.operation.hash(state); self.metadata.hash(state); self.plan_options.hash(state); + self.config_mode.hash(state); + self.introspection.hash(state); } } @@ -572,6 +622,8 @@ pub(crate) struct WarmUpCachingQueryKey { pub(crate) hash: Option>, pub(crate) metadata: CacheKeyMetadata, pub(crate) plan_options: PlanOptions, + pub(crate) config_mode: ConfigMode, + pub(crate) introspection: bool, } #[cfg(test)] diff --git a/apollo-router/src/query_planner/convert.rs b/apollo-router/src/query_planner/convert.rs index 79478bb120..2f24e35b9d 100644 --- a/apollo-router/src/query_planner/convert.rs +++ b/apollo-router/src/query_planner/convert.rs @@ -12,7 +12,10 @@ use crate::query_planner::subscription; impl From<&'_ next::QueryPlan> for bridge::QueryPlan { fn from(value: &'_ next::QueryPlan) -> Self { - let next::QueryPlan { node } = value; + let next::QueryPlan { + node, + statistics: _, + } = value; Self { node: option(node) } } } @@ -26,7 +29,7 @@ impl From<&'_ next::TopLevelPlanNode> for plan::PlanNode { next::TopLevelPlanNode::Parallel(node) => node.into(), next::TopLevelPlanNode::Flatten(node) => node.into(), next::TopLevelPlanNode::Defer(node) => node.into(), - next::TopLevelPlanNode::Condition(node) => node.into(), + next::TopLevelPlanNode::Condition(node) => node.as_ref().into(), } } } @@ -34,28 +37,33 @@ impl From<&'_ next::TopLevelPlanNode> for plan::PlanNode { impl From<&'_ next::PlanNode> for plan::PlanNode { fn from(value: &'_ next::PlanNode) -> Self { match value { - next::PlanNode::Fetch(node) => node.as_ref().into(), - next::PlanNode::Sequence(node) => node.as_ref().into(), - next::PlanNode::Parallel(node) => node.as_ref().into(), - next::PlanNode::Flatten(node) => node.as_ref().into(), - next::PlanNode::Defer(node) => node.as_ref().into(), + next::PlanNode::Fetch(node) => node.into(), + next::PlanNode::Sequence(node) => node.into(), + next::PlanNode::Parallel(node) => node.into(), + next::PlanNode::Flatten(node) => node.into(), + next::PlanNode::Defer(node) => node.into(), next::PlanNode::Condition(node) => node.as_ref().into(), } } } +impl From<&'_ Box> for plan::PlanNode { + fn from(value: &'_ Box) -> Self { + value.as_ref().into() + } +} impl From<&'_ next::SubscriptionNode> for plan::PlanNode { fn from(value: &'_ next::SubscriptionNode) -> Self { let next::SubscriptionNode { primary, rest } = value; Self::Subscription { - primary: primary.into(), + primary: primary.as_ref().into(), rest: option(rest).map(Box::new), } } } -impl From<&'_ next::FetchNode> for plan::PlanNode { - fn from(value: &'_ next::FetchNode) -> Self { +impl From<&'_ Box> for plan::PlanNode { + fn from(value: &'_ Box) -> Self { let next::FetchNode { subgraph_name, id, @@ -66,16 +74,16 @@ impl From<&'_ next::FetchNode> for plan::PlanNode { operation_kind, input_rewrites, output_rewrites, - } = value; + } = &**value; Self::Fetch(super::fetch::FetchNode { service_name: subgraph_name.clone(), - requires: vec(requires), + requires: requires.as_deref().map(vec).unwrap_or_default(), variable_usages: variable_usages.iter().map(|v| v.clone().into()).collect(), // TODO: use Arc in apollo_federation to avoid this clone operation: SubgraphOperation::from_parsed(Arc::new(operation_document.clone())), operation_name: operation_name.clone(), operation_kind: (*operation_kind).into(), - id: id.clone(), + id: id.map(|id| id.to_string().into()), input_rewrites: option_vec(input_rewrites), output_rewrites: option_vec(output_rewrites), schema_aware_hash: Default::default(), @@ -241,7 +249,11 @@ impl From<&'_ executable::Field> for selection::Field { Self { alias: alias.clone(), name: name.clone(), - selections: option_vec(&selection_set.selections), + selections: if selection_set.selections.is_empty() { + None + } else { + Some(vec(&selection_set.selections)) + }, } } } @@ -260,9 +272,9 @@ impl From<&'_ executable::InlineFragment> for selection::InlineFragment { } } -impl From<&'_ next::FetchDataRewrite> for rewrites::DataRewrite { - fn from(value: &'_ next::FetchDataRewrite) -> Self { - match value { +impl From<&'_ Arc> for rewrites::DataRewrite { + fn from(value: &'_ Arc) -> Self { + match value.as_ref() { next::FetchDataRewrite::ValueSetter(setter) => Self::ValueSetter(setter.into()), next::FetchDataRewrite::KeyRenamer(renamer) => Self::KeyRenamer(renamer.into()), } diff --git a/apollo-router/src/query_planner/tests.rs b/apollo-router/src/query_planner/tests.rs index 02cc1ce6c2..3431fda2b5 100644 --- a/apollo-router/src/query_planner/tests.rs +++ b/apollo-router/src/query_planner/tests.rs @@ -425,9 +425,10 @@ async fn defer_if_condition() { let schema = include_str!("testdata/defer_clause.graphql"); // we need to use the planner here instead of Schema::parse_test because that one uses the router bridge's api_schema function // does not keep the defer directive definition - let planner = BridgeQueryPlanner::new(schema.to_string(), Arc::new(Configuration::default())) - .await - .unwrap(); + let planner = + BridgeQueryPlanner::new(schema.to_string(), Arc::new(Configuration::default()), None) + .await + .unwrap(); let schema = planner.schema(); let root: PlanNode = diff --git a/apollo-router/src/services/http/service.rs b/apollo-router/src/services/http/service.rs index cb6eba3a34..ff8b9c984d 100644 --- a/apollo-router/src/services/http/service.rs +++ b/apollo-router/src/services/http/service.rs @@ -295,7 +295,7 @@ impl tower::Service for HttpClientService { let signing_params = context .extensions() .lock() - .get::() + .get::>() .cloned(); Box::pin(async move { diff --git a/apollo-router/src/services/subgraph_service.rs b/apollo-router/src/services/subgraph_service.rs index 3e285ca664..3e484603fe 100644 --- a/apollo-router/src/services/subgraph_service.rs +++ b/apollo-router/src/services/subgraph_service.rs @@ -529,7 +529,7 @@ async fn call_websocket( let signing_params = context .extensions() .lock() - .get::() + .get::>() .cloned(); let request = if let Some(signing_params) = signing_params { @@ -764,11 +764,11 @@ fn http_response_to_graphql_response( } /// Process a single subgraph batch request -#[instrument(skip(client_factory, context, request))] +#[instrument(skip(client_factory, contexts, request))] pub(crate) async fn process_batch( client_factory: HttpClientServiceFactory, service: String, - context: Context, + mut contexts: Vec, mut request: http::Request, listener_count: usize, ) -> Result, FetchError> { @@ -810,7 +810,13 @@ pub(crate) async fn process_batch( // 2. If an HTTP status is not 2xx it will always be attached as a graphql error. // 3. If the response type is `application/json` and status is not 2xx and the body the entire body will be output if the response is not valid graphql. - let display_body = context.contains_key(LOGGING_DISPLAY_BODY); + // We need a "representative context" for a batch. We use the first context in our list of + // contexts + let batch_context = contexts + .first() + .expect("we have at least one context in the batch") + .clone(); + let display_body = batch_context.contains_key(LOGGING_DISPLAY_BODY); let client = client_factory.create(&service); // Update our batching metrics (just before we fetch) @@ -826,11 +832,12 @@ pub(crate) async fn process_batch( // Perform the actual fetch. If this fails then we didn't manage to make the call at all, so we can't do anything with it. tracing::debug!("fetching from subgraph: {service}"); - let (parts, content_type, body) = do_fetch(client, &context, &service, request, display_body) - .instrument(subgraph_req_span) - .await?; + let (parts, content_type, body) = + do_fetch(client, &batch_context, &service, request, display_body) + .instrument(subgraph_req_span) + .await?; - let subgraph_response_event = context + let subgraph_response_event = batch_context .extensions() .lock() .get::() @@ -914,6 +921,21 @@ pub(crate) async fn process_batch( } tracing::debug!("we have a vec of graphql_responses: {graphql_responses:?}"); + // Before we process our graphql responses, ensure that we have a context for each + // response + if graphql_responses.len() != contexts.len() { + return Err(FetchError::SubrequestBatchingError { + service, + reason: format!( + "number of contexts ({}) is not equal to number of graphql responses ({})", + contexts.len(), + graphql_responses.len() + ), + }); + } + + // We are going to pop contexts from the back, so let's reverse our contexts + contexts.reverse(); // Build an http Response for each graphql response let subgraph_responses: Result, _> = graphql_responses .into_iter() @@ -924,7 +946,9 @@ pub(crate) async fn process_batch( .body(res) .map(|mut http_res| { *http_res.headers_mut() = parts.headers.clone(); - let resp = SubgraphResponse::new_from_response(http_res, context.clone()); + // Use the original context for the request to create the response + let context = contexts.pop().expect("we have a context for each response"); + let resp = SubgraphResponse::new_from_response(http_res, context); tracing::debug!("we have a resp: {resp:?}"); resp @@ -1002,7 +1026,7 @@ pub(crate) async fn notify_batch_query( } type BatchInfo = ( - (String, http::Request, Context, usize), + (String, http::Request, Vec, usize), Vec>>, ); @@ -1016,9 +1040,9 @@ pub(crate) async fn process_batches( let mut errors = vec![]; let (info, txs): (Vec<_>, Vec<_>) = futures::future::join_all(svc_map.into_iter().map(|(service, requests)| async { - let (_op_name, context, request, txs) = assemble_batch(requests).await?; + let (_op_name, contexts, request, txs) = assemble_batch(requests).await?; - Ok(((service, request, context, txs.len()), txs)) + Ok(((service, request, contexts, txs.len()), txs)) })) .await .into_iter() @@ -1047,11 +1071,11 @@ pub(crate) async fn process_batches( .into()); } let batch_futures = info.into_iter().zip_eq(txs).map( - |((service, request, context, listener_count), senders)| async move { + |((service, request, contexts, listener_count), senders)| async move { let batch_result = process_batch( cf.clone(), service.clone(), - context, + contexts, request, listener_count, ) diff --git a/apollo-router/src/services/supergraph/snapshots/apollo_router__services__supergraph__tests__invalid_input_enum-2.snap b/apollo-router/src/services/supergraph/snapshots/apollo_router__services__supergraph__tests__invalid_input_enum-2.snap new file mode 100644 index 0000000000..40b8fa4908 --- /dev/null +++ b/apollo-router/src/services/supergraph/snapshots/apollo_router__services__supergraph__tests__invalid_input_enum-2.snap @@ -0,0 +1,15 @@ +--- +source: apollo-router/src/services/supergraph/tests.rs +expression: response +--- +{ + "errors": [ + { + "message": "invalid type for variable: 'input'", + "extensions": { + "name": "input", + "code": "VALIDATION_INVALID_TYPE_VARIABLE" + } + } + ] +} diff --git a/apollo-router/src/services/supergraph/snapshots/apollo_router__services__supergraph__tests__invalid_input_enum.snap b/apollo-router/src/services/supergraph/snapshots/apollo_router__services__supergraph__tests__invalid_input_enum.snap new file mode 100644 index 0000000000..0e2dd5d122 --- /dev/null +++ b/apollo-router/src/services/supergraph/snapshots/apollo_router__services__supergraph__tests__invalid_input_enum.snap @@ -0,0 +1,20 @@ +--- +source: apollo-router/src/services/supergraph/tests.rs +expression: response +--- +{ + "errors": [ + { + "message": "Value \"C does not exist in \"InputEnum\" enum.", + "locations": [ + { + "line": 1, + "column": 21 + } + ], + "extensions": { + "code": "GRAPHQL_VALIDATION_FAILED" + } + } + ] +} diff --git a/apollo-router/src/services/supergraph/snapshots/apollo_router__services__supergraph__tests__query_reconstruction.snap b/apollo-router/src/services/supergraph/snapshots/apollo_router__services__supergraph__tests__query_reconstruction.snap index ff64cc1b5e..34f783cd01 100644 --- a/apollo-router/src/services/supergraph/snapshots/apollo_router__services__supergraph__tests__query_reconstruction.snap +++ b/apollo-router/src/services/supergraph/snapshots/apollo_router__services__supergraph__tests__query_reconstruction.snap @@ -1,5 +1,5 @@ --- -source: apollo-router/src/services/supergraph_service.rs +source: apollo-router/src/services/supergraph/tests.rs expression: stream.next_response().await.unwrap() --- { diff --git a/apollo-router/src/services/supergraph/tests.rs b/apollo-router/src/services/supergraph/tests.rs index df88bc123e..5d0f113a5e 100644 --- a/apollo-router/src/services/supergraph/tests.rs +++ b/apollo-router/src/services/supergraph/tests.rs @@ -3549,3 +3549,76 @@ async fn abstract_types_in_requires() { let mut stream = service.oneshot(request).await.unwrap(); insta::assert_json_snapshot!(stream.next_response().await.unwrap()); } + +const ENUM_SCHEMA: &str = r#"schema + @core(feature: "https://specs.apollo.dev/core/v0.1") + @core(feature: "https://specs.apollo.dev/join/v0.1") + @core(feature: "https://specs.apollo.dev/inaccessible/v0.1") + { + query: Query + } + directive @core(feature: String!) repeatable on SCHEMA + directive @join__field(graph: join__Graph, requires: join__FieldSet, provides: join__FieldSet) on FIELD_DEFINITION + directive @join__type(graph: join__Graph!, key: join__FieldSet) repeatable on OBJECT | INTERFACE + directive @join__owner(graph: join__Graph!) on OBJECT | INTERFACE + directive @join__graph(name: String!, url: String!) on ENUM_VALUE + directive @inaccessible on OBJECT | FIELD_DEFINITION | INTERFACE | UNION + scalar join__FieldSet + enum join__Graph { + USER @join__graph(name: "user", url: "http://localhost:4001/graphql") + ORGA @join__graph(name: "orga", url: "http://localhost:4002/graphql") + } + type Query { + test(input: InputEnum): String @join__field(graph: USER) + } + + enum InputEnum { + A + B + }"#; + +#[tokio::test] +async fn invalid_input_enum() { + let service = TestHarness::builder() + .configuration_json(serde_json::json!({"include_subgraph_errors": { "all": true } })) + .unwrap() + .schema(ENUM_SCHEMA) + //.extra_plugin(subgraphs) + .build_supergraph() + .await + .unwrap(); + + let request = supergraph::Request::fake_builder() + .query("query { test(input: C) }") + .context(defer_context()) + // Request building here + .build() + .unwrap(); + let response = service + .clone() + .oneshot(request) + .await + .unwrap() + .next_response() + .await + .unwrap(); + + insta::assert_json_snapshot!(response); + + let request = supergraph::Request::fake_builder() + .query("query($input: InputEnum) { test(input: $input) }") + .variable("input", "INVALID") + .context(defer_context()) + // Request building here + .build() + .unwrap(); + let response = service + .oneshot(request) + .await + .unwrap() + .next_response() + .await + .unwrap(); + + insta::assert_json_snapshot!(response); +} diff --git a/apollo-router/src/spec/field_type.rs b/apollo-router/src/spec/field_type.rs index e14dc10b05..9e40b47a86 100644 --- a/apollo-router/src/spec/field_type.rs +++ b/apollo-router/src/spec/field_type.rs @@ -134,11 +134,10 @@ fn validate_input_value( // Custom scalar: accept any JSON value (schema::ExtendedType::Scalar(_), _) => Ok(()), - // TODO: check enum value? - // (schema::ExtendedType::Enum(def), Value::String(s)) => { - // from_bool(def.values.contains_key(s)) - // }, - (schema::ExtendedType::Enum(_), _) => Ok(()), + (schema::ExtendedType::Enum(def), Value::String(s)) => { + from_bool(def.values.contains_key(s.as_str())) + } + (schema::ExtendedType::Enum(_), _) => Err(InvalidValue), (schema::ExtendedType::InputObject(def), Value::Object(obj)) => { // TODO: check keys in `obj` but not in `def.fields`? diff --git a/apollo-router/src/uplink/license_stream.rs b/apollo-router/src/uplink/license_stream.rs index 8ee437cb89..a07a497e98 100644 --- a/apollo-router/src/uplink/license_stream.rs +++ b/apollo-router/src/uplink/license_stream.rs @@ -448,9 +448,9 @@ mod test { tokio::task::spawn(async move { // This simulates a new claim coming in before in between the warning and halt - let _ = tx.send(license_with_claim(15, 45)).await; - tokio::time::sleep(Duration::from_millis(20)).await; - let _ = tx.send(license_with_claim(15, 30)).await; + let _ = tx.send(license_with_claim(100, 300)).await; + tokio::time::sleep(Duration::from_millis(200)).await; + let _ = tx.send(license_with_claim(100, 300)).await; }); let events = events_stream.collect::>().await; assert_eq!( diff --git a/apollo-router/tests/common.rs b/apollo-router/tests/common.rs index 84ec7a5434..b10e484b94 100644 --- a/apollo-router/tests/common.rs +++ b/apollo-router/tests/common.rs @@ -9,6 +9,13 @@ use std::sync::Mutex; use std::time::Duration; use buildstructor::buildstructor; +use fred::clients::RedisClient; +use fred::interfaces::ClientLike; +use fred::interfaces::KeysInterface; +use fred::prelude::RedisConfig; +use fred::types::ScanType; +use fred::types::Scanner; +use futures::StreamExt; use http::header::ACCEPT; use http::header::CONTENT_TYPE; use http::HeaderValue; @@ -81,6 +88,7 @@ pub struct IntegrationTest { _subgraph_overrides: HashMap, bind_address: Arc>>, + redis_namespace: String, } impl IntegrationTest { @@ -272,6 +280,7 @@ impl IntegrationTest { supergraph: Option, mut subgraph_overrides: HashMap, ) -> Self { + let redis_namespace = Uuid::new_v4().to_string(); let telemetry = telemetry.unwrap_or_default(); let tracer_provider_client = telemetry.tracer_provider("client"); let subscriber_client = Self::dispatch(&tracer_provider_client); @@ -285,7 +294,7 @@ impl IntegrationTest { subgraph_overrides.entry("products".into()).or_insert(url); // Insert the overrides into the config - let config_str = merge_overrides(&config, &subgraph_overrides, None); + let config_str = merge_overrides(&config, &subgraph_overrides, None, &redis_namespace); let supergraph = supergraph.unwrap_or(PathBuf::from_iter([ "..", @@ -334,6 +343,7 @@ impl IntegrationTest { subscriber_client, _tracer_provider_subgraph: tracer_provider_subgraph, telemetry, + redis_namespace, } } @@ -456,7 +466,12 @@ impl IntegrationTest { pub async fn update_config(&self, yaml: &str) { tokio::fs::write( &self.test_config_location, - &merge_overrides(yaml, &self._subgraph_overrides, Some(self.bind_address())), + &merge_overrides( + yaml, + &self._subgraph_overrides, + Some(self.bind_address()), + &self.redis_namespace, + ), ) .await .expect("must be able to write config"); @@ -894,6 +909,76 @@ impl IntegrationTest { } } } + + #[allow(dead_code)] + pub async fn clear_redis_cache(&self) { + let config = RedisConfig::from_url("redis://127.0.0.1:6379").unwrap(); + + let client = RedisClient::new(config, None, None, None); + let connection_task = client.connect(); + client + .wait_for_connect() + .await + .expect("could not connect to redis"); + let namespace = &self.redis_namespace; + let mut scan = client.scan(format!("{namespace}:*"), None, Some(ScanType::String)); + while let Some(result) = scan.next().await { + if let Some(page) = result.expect("could not scan redis").take_results() { + for key in page { + let key = key.as_str().expect("key should be a string"); + if key.starts_with(&self.redis_namespace) { + client + .del::(key) + .await + .expect("could not delete key"); + } + } + } + } + + client.quit().await.expect("could not quit redis"); + // calling quit ends the connection and event listener tasks + let _ = connection_task.await; + } + + #[allow(dead_code)] + pub async fn assert_redis_cache_contains(&self, key: &str, ignore: Option<&str>) -> String { + let config = RedisConfig::from_url("redis://127.0.0.1:6379").unwrap(); + let client = RedisClient::new(config, None, None, None); + let connection_task = client.connect(); + client.wait_for_connect().await.unwrap(); + let redis_namespace = &self.redis_namespace; + let namespaced_key = format!("{redis_namespace}:{key}"); + let s = match client.get(&namespaced_key).await { + Ok(s) => s, + Err(e) => { + println!("non-ignored keys in the same namespace in Redis:"); + + let mut scan = client.scan( + format!("{redis_namespace}:*"), + Some(u32::MAX), + Some(ScanType::String), + ); + + while let Some(result) = scan.next().await { + let keys = result.as_ref().unwrap().results().as_ref().unwrap(); + for key in keys { + let key = key.as_str().expect("key should be a string"); + let unnamespaced_key = key.replace(&format!("{redis_namespace}:"), ""); + if Some(unnamespaced_key.as_str()) != ignore { + println!("\t{unnamespaced_key}"); + } + } + } + panic!("key {key} not found: {e}\n This may be caused by a number of things including federation version changes"); + } + }; + + client.quit().await.unwrap(); + // calling quit ends the connection and event listener tasks + let _ = connection_task.await; + s + } } impl Drop for IntegrationTest { @@ -926,6 +1011,7 @@ fn merge_overrides( yaml: &str, subgraph_overrides: &HashMap, bind_addr: Option, + redis_namespace: &str, ) -> String { let bind_addr = bind_addr .map(|a| a.to_string()) @@ -1005,5 +1091,20 @@ fn merge_overrides( json!({"listen": bind_addr.to_string()}), ); + // Set query plan redis namespace + if let Some(query_plan) = config + .as_object_mut() + .and_then(|o| o.get_mut("supergraph")) + .and_then(|o| o.as_object_mut()) + .and_then(|o| o.get_mut("query_planning")) + .and_then(|o| o.as_object_mut()) + .and_then(|o| o.get_mut("cache")) + .and_then(|o| o.as_object_mut()) + .and_then(|o| o.get_mut("redis")) + .and_then(|o| o.as_object_mut()) + { + query_plan.insert("namespace".to_string(), redis_namespace.into()); + } + serde_yaml::to_string(&config).unwrap() } diff --git a/apollo-router/tests/fixtures/request_response_test.rhai b/apollo-router/tests/fixtures/request_response_test.rhai index 3601accf05..417948f83e 100644 --- a/apollo-router/tests/fixtures/request_response_test.rhai +++ b/apollo-router/tests/fixtures/request_response_test.rhai @@ -123,6 +123,15 @@ fn process_execution_response(response) { fn process_subgraph_response(response) { process_common_response(response); + if type_of(response.status_code) != "http::status::StatusCode" { + throw(`status_code: expected: "http::status::StatusCode", actual: ${type_of(response.status_code)}`); + } + if parse_int(response.status_code.to_string()) != 200 { + throw(`status_code: expected: 200, actual: ${response.status_code}`); + } + if response.status_code != status_code_from_int(200) { + throw(`status_code: expected: 200, actual: ${response.status_code}`); + } } fn process_subgraph_response_om_forbidden(response) { diff --git a/apollo-router/tests/integration/coprocessor.rs b/apollo-router/tests/integration/coprocessor.rs new file mode 100644 index 0000000000..d8d2ffadb0 --- /dev/null +++ b/apollo-router/tests/integration/coprocessor.rs @@ -0,0 +1,23 @@ +use insta::assert_yaml_snapshot; +use tower::BoxError; + +use crate::integration::IntegrationTest; + +#[tokio::test(flavor = "multi_thread")] +async fn test_error_not_propagated_to_client() -> Result<(), BoxError> { + let mut router = IntegrationTest::builder() + .config(include_str!("fixtures/broken_coprocessor.router.yaml")) + .build() + .await; + + router.start().await; + router.assert_started().await; + + let (_trace_id, response) = router.execute_default_query().await; + assert_eq!(response.status(), 500); + assert_yaml_snapshot!(response.text().await?); + router.assert_log_contains("INTERNAL_SERVER_ERROR").await; + router.graceful_shutdown().await; + + Ok(()) +} diff --git a/apollo-router/tests/integration/fixtures/broken_coprocessor.router.yaml b/apollo-router/tests/integration/fixtures/broken_coprocessor.router.yaml new file mode 100644 index 0000000000..3f65d91227 --- /dev/null +++ b/apollo-router/tests/integration/fixtures/broken_coprocessor.router.yaml @@ -0,0 +1,6 @@ +# This coprocessor doesn't point to anything +coprocessor: + url: "http://unreachable.example" + router: + request: + body: true \ No newline at end of file diff --git a/apollo-router/tests/integration/fixtures/query_planner_redis_config_update.router.yaml b/apollo-router/tests/integration/fixtures/query_planner_redis_config_update.router.yaml new file mode 100644 index 0000000000..f0900985d1 --- /dev/null +++ b/apollo-router/tests/integration/fixtures/query_planner_redis_config_update.router.yaml @@ -0,0 +1,9 @@ +# This config is used for testing query plans to redis +supergraph: + query_planning: + cache: + redis: + required_to_start: true + urls: + - redis://localhost:6379 + ttl: 10s \ No newline at end of file diff --git a/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_defer.router.yaml b/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_defer.router.yaml new file mode 100644 index 0000000000..abe5e560eb --- /dev/null +++ b/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_defer.router.yaml @@ -0,0 +1,11 @@ +# This config updates the query plan options so that we can see if there is a different redis cache entry generted for query plans +supergraph: + query_planning: + cache: + redis: + required_to_start: true + urls: + - redis://localhost:6379 + ttl: 10s + defer_support: false + diff --git a/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_introspection.router.yaml b/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_introspection.router.yaml new file mode 100644 index 0000000000..cd9a3e2d4d --- /dev/null +++ b/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_introspection.router.yaml @@ -0,0 +1,11 @@ +# This config updates the query plan options so that we can see if there is a different redis cache entry generted for query plans +supergraph: + introspection: true + query_planning: + cache: + redis: + required_to_start: true + urls: + - redis://localhost:6379 + ttl: 10s + diff --git a/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_query_fragments.router.yaml b/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_query_fragments.router.yaml new file mode 100644 index 0000000000..ffb1e49152 --- /dev/null +++ b/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_query_fragments.router.yaml @@ -0,0 +1,11 @@ +# This config updates the query plan options so that we can see if there is a different redis cache entry generted for query plans +supergraph: + query_planning: + cache: + redis: + required_to_start: true + urls: + - redis://localhost:6379 + ttl: 10s + generate_query_fragments: true + diff --git a/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_query_planner_mode.router.yaml b/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_query_planner_mode.router.yaml new file mode 100644 index 0000000000..704cf8fb60 --- /dev/null +++ b/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_query_planner_mode.router.yaml @@ -0,0 +1,12 @@ +# This config updates the query plan options so that we can see if there is a different redis cache entry generted for query plans +supergraph: + query_planning: + cache: + redis: + required_to_start: true + urls: + - redis://localhost:6379 + ttl: 10s + +experimental_query_planner_mode: new +experimental_apollo_metrics_generation_mode: new \ No newline at end of file diff --git a/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_reuse_query_fragments.router.yaml b/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_reuse_query_fragments.router.yaml new file mode 100644 index 0000000000..14136a0268 --- /dev/null +++ b/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_reuse_query_fragments.router.yaml @@ -0,0 +1,11 @@ +# This config updates the query plan options so that we can see if there is a different redis cache entry generted for query plans +supergraph: + query_planning: + cache: + redis: + required_to_start: true + urls: + - redis://localhost:6379 + ttl: 10s + experimental_reuse_query_fragments: true + diff --git a/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_type_conditional_fetching.router.yaml b/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_type_conditional_fetching.router.yaml new file mode 100644 index 0000000000..5a83d5d692 --- /dev/null +++ b/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_type_conditional_fetching.router.yaml @@ -0,0 +1,11 @@ +# This config updates the query plan options so that we can see if there is a different redis cache entry generted for query plans +supergraph: + query_planning: + cache: + redis: + required_to_start: true + urls: + - redis://localhost:6379 + ttl: 10s +experimental_type_conditioned_fetching: true + diff --git a/apollo-router/tests/integration/mod.rs b/apollo-router/tests/integration/mod.rs index 97937bac53..f3432b12c4 100644 --- a/apollo-router/tests/integration/mod.rs +++ b/apollo-router/tests/integration/mod.rs @@ -3,10 +3,13 @@ mod batching; pub(crate) mod common; pub(crate) use common::IntegrationTest; +mod coprocessor; mod docs; mod file_upload; mod lifecycle; mod operation_limits; + +#[cfg(any(not(feature = "ci"), all(target_arch = "x86_64", target_os = "linux")))] mod redis; mod rhai; mod subscription; diff --git a/apollo-router/tests/integration/redis.rs b/apollo-router/tests/integration/redis.rs index afa898d30c..4b3aa46d78 100644 --- a/apollo-router/tests/integration/redis.rs +++ b/apollo-router/tests/integration/redis.rs @@ -1,178 +1,118 @@ -#[cfg(all(target_os = "linux", target_arch = "x86_64", test))] -mod test { - use apollo_router::plugin::test::MockSubgraph; - use apollo_router::services::execution::QueryPlan; - use apollo_router::services::router; - use apollo_router::services::supergraph; - use apollo_router::Context; - use apollo_router::MockedSubgraphs; - use fred::cmd; - use fred::prelude::*; - use fred::types::ScanType; - use fred::types::Scanner; - use futures::StreamExt; - use http::header::CACHE_CONTROL; - use http::HeaderValue; - use http::Method; - use serde::Deserialize; - use serde::Serialize; - use serde_json::json; - use serde_json::Value; - use tower::BoxError; - use tower::ServiceExt; - - #[tokio::test(flavor = "multi_thread")] - async fn query_planner() -> Result<(), BoxError> { - // If this test fails and the cache key format changed you'll need to update the key here. - // 1. Force this test to run locally by removing the cfg() line at the top of this file. - // 2. run `docker compose up -d` and connect to the redis container by running `docker-compose exec redis /bin/bash`. - // 3. Run the `redis-cli` command from the shell and start the redis `monitor` command. - // 4. Run this test and yank the updated cache key from the redis logs. - let known_cache_key = "plan:v2.7.5:16385ebef77959fcdc520ad507eb1f7f7df28f1d54a0569e3adabcb4cd00d7ce:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:5c7a72fa35639949328548d77b56dba2e77d0dfa90c19b69978da119e996bb92"; - - let config = RedisConfig::from_url("redis://127.0.0.1:6379").unwrap(); - let client = RedisClient::new(config, None, None, None); - let connection_task = client.connect(); - client.wait_for_connect().await.unwrap(); - - client.del::(known_cache_key).await.unwrap(); - - let supergraph = apollo_router::TestHarness::builder() - .with_subgraph_network_requests() - .configuration_json(json!({ - "supergraph": { - "query_planning": { - "cache": { - "in_memory": { - "limit": 2 - }, - "redis": { - "urls": ["redis://127.0.0.1:6379"], - "ttl": "10s" - } +use apollo_router::plugin::test::MockSubgraph; +use apollo_router::services::execution::QueryPlan; +use apollo_router::services::router; +use apollo_router::services::supergraph; +use apollo_router::Context; +use apollo_router::MockedSubgraphs; +use fred::cmd; +use fred::prelude::*; +use fred::types::ScanType; +use fred::types::Scanner; +use futures::StreamExt; +use http::header::CACHE_CONTROL; +use http::HeaderValue; +use http::Method; +use serde::Deserialize; +use serde::Serialize; +use serde_json::json; +use serde_json::Value; +use tower::BoxError; +use tower::ServiceExt; + +use crate::integration::IntegrationTest; + +#[tokio::test(flavor = "multi_thread")] +async fn query_planner() -> Result<(), BoxError> { + // If this test fails and the cache key format changed you'll need to update the key here. + // 1. Force this test to run locally by removing the cfg() line at the top of this file. + // 2. run `docker compose up -d` and connect to the redis container by running `docker-compose exec redis /bin/bash`. + // 3. Run the `redis-cli` command from the shell and start the redis `monitor` command. + // 4. Run this test and yank the updated cache key from the redis logs. + let known_cache_key = "plan:0:v2.7.5:16385ebef77959fcdc520ad507eb1f7f7df28f1d54a0569e3adabcb4cd00d7ce:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:9c26cb1f820a78848ba3d5d3295c16aa971368c5295422fd33cc19d4a6006a9c"; + + let config = RedisConfig::from_url("redis://127.0.0.1:6379").unwrap(); + let client = RedisClient::new(config, None, None, None); + let connection_task = client.connect(); + client.wait_for_connect().await.unwrap(); + + client.del::(known_cache_key).await.unwrap(); + + let supergraph = apollo_router::TestHarness::builder() + .with_subgraph_network_requests() + .configuration_json(json!({ + "supergraph": { + "query_planning": { + "cache": { + "in_memory": { + "limit": 2 + }, + "redis": { + "urls": ["redis://127.0.0.1:6379"], + "ttl": "10s" } } } - })) - .unwrap() - .schema(include_str!("../fixtures/supergraph.graphql")) - .build_supergraph() - .await - .unwrap(); - - let request = supergraph::Request::fake_builder() - .query(r#"{ topProducts { name name2:name } }"#) - .method(Method::POST) - .build() - .unwrap(); - - let _ = supergraph - .oneshot(request) - .await - .unwrap() - .next_response() - .await; - - let s: String = match client.get(known_cache_key).await { - Ok(s) => s, - Err(e) => { - println!("keys in Redis server:"); - let mut scan = client.scan("plan:*", None, Some(ScanType::String)); - while let Some(key) = scan.next().await { - let key = key.as_ref().unwrap().results(); - println!("\t{key:?}"); - } - panic!("key {known_cache_key} not found: {e}\nIf you see this error, make sure the federation version you use matches the redis key."); } - }; - let exp: i64 = client - .custom_raw(cmd!("EXPIRETIME"), vec![known_cache_key.to_string()]) - .await - .and_then(|frame| frame.try_into()) - .and_then(|value: RedisValue| value.convert()) - .unwrap(); - let query_plan_res: serde_json::Value = serde_json::from_str(&s).unwrap(); - // ignore the usage reporting field for which the order of elements in `referenced_fields_by_type` can change - let query_plan = query_plan_res - .as_object() - .unwrap() - .get("Ok") - .unwrap() - .get("Plan") - .unwrap() - .get("plan") - .unwrap() - .get("root"); - - insta::assert_json_snapshot!(query_plan); - - // test expiration refresh - tokio::time::sleep(std::time::Duration::from_secs(1)).await; - let supergraph = apollo_router::TestHarness::builder() - .with_subgraph_network_requests() - .configuration_json(json!({ - "supergraph": { - "query_planning": { - "cache": { - "in_memory": { - "limit": 2 - }, - "redis": { - "urls": ["redis://127.0.0.1:6379"], - "ttl": "10s" - } - } - } - } - })) - .unwrap() - .schema(include_str!("../fixtures/supergraph.graphql")) - .build_supergraph() - .await - .unwrap(); - - let request = supergraph::Request::fake_builder() - .query(r#"{ topProducts { name name2:name } }"#) - .method(Method::POST) - .build() - .unwrap(); - let _ = supergraph - .oneshot(request) - .await - .unwrap() - .next_response() - .await; - let new_exp: i64 = client - .custom_raw(cmd!("EXPIRETIME"), vec![known_cache_key.to_string()]) - .await - .and_then(|frame| frame.try_into()) - .and_then(|value: RedisValue| value.convert()) - .unwrap(); - - assert!(exp < new_exp); - - client.quit().await.unwrap(); - // calling quit ends the connection and event listener tasks - let _ = connection_task.await; - Ok(()) - } - - #[derive(Deserialize, Serialize)] - - struct QueryPlannerContent { - plan: QueryPlan, - } - - #[tokio::test(flavor = "multi_thread")] - async fn apq() -> Result<(), BoxError> { - let config = RedisConfig::from_url("redis://127.0.0.1:6379").unwrap(); - let client = RedisClient::new(config, None, None, None); - let connection_task = client.connect(); - client.wait_for_connect().await.unwrap(); - - let config = json!({ - "apq": { - "router": { + })) + .unwrap() + .schema(include_str!("../fixtures/supergraph.graphql")) + .build_supergraph() + .await + .unwrap(); + + let request = supergraph::Request::fake_builder() + .query(r#"{ topProducts { name name2:name } }"#) + .method(Method::POST) + .build() + .unwrap(); + + let _ = supergraph + .oneshot(request) + .await + .unwrap() + .next_response() + .await; + + let s: String = match client.get(known_cache_key).await { + Ok(s) => s, + Err(e) => { + println!("keys in Redis server:"); + let mut scan = client.scan("plan:*", Some(u32::MAX), Some(ScanType::String)); + while let Some(key) = scan.next().await { + let key = key.as_ref().unwrap().results(); + println!("\t{key:?}"); + } + panic!("key {known_cache_key} not found: {e}\nIf you see this error, make sure the federation version you use matches the redis key."); + } + }; + let exp: i64 = client + .custom_raw(cmd!("EXPIRETIME"), vec![known_cache_key.to_string()]) + .await + .and_then(|frame| frame.try_into()) + .and_then(|value: RedisValue| value.convert()) + .unwrap(); + let query_plan_res: serde_json::Value = serde_json::from_str(&s).unwrap(); + // ignore the usage reporting field for which the order of elements in `referenced_fields_by_type` can change + let query_plan = query_plan_res + .as_object() + .unwrap() + .get("Ok") + .unwrap() + .get("Plan") + .unwrap() + .get("plan") + .unwrap() + .get("root"); + + insta::assert_json_snapshot!(query_plan); + + // test expiration refresh + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + let supergraph = apollo_router::TestHarness::builder() + .with_subgraph_network_requests() + .configuration_json(json!({ + "supergraph": { + "query_planning": { "cache": { "in_memory": { "limit": 2 @@ -184,155 +124,215 @@ mod test { } } } - }); - - let router = apollo_router::TestHarness::builder() - .with_subgraph_network_requests() - .configuration_json(config.clone()) - .unwrap() - .schema(include_str!("../fixtures/supergraph.graphql")) - .build_router() - .await - .unwrap(); - - let query_hash = "4c45433039407593557f8a982dafd316a66ec03f0e1ed5fa1b7ef8060d76e8ec"; - - client - .del::(&format!("apq:{query_hash}")) - .await - .unwrap(); - - let persisted = json!({ - "version" : 1, - "sha256Hash" : query_hash - }); - - // an APQ should fail if we do not know about the hash - // it should not set a value in Redis - let request: router::Request = supergraph::Request::fake_builder() - .extension("persistedQuery", persisted.clone()) - .method(Method::POST) - .build() - .unwrap() - .try_into() - .unwrap(); - - let res = router - .clone() - .oneshot(request) - .await - .unwrap() - .into_graphql_response_stream() - .await - .next() - .await - .unwrap() - .unwrap(); - assert_eq!( - res.errors.first().unwrap().message, - "PersistedQueryNotFound" - ); - let r: Option = client.get(&format!("apq:{query_hash}")).await.unwrap(); - assert!(r.is_none()); - - // Now we register the query - // it should set a value in Redis - let request: router::Request = supergraph::Request::fake_builder() - .query(r#"{ topProducts { name name2:name } }"#) - .extension("persistedQuery", persisted.clone()) - .method(Method::POST) - .build() - .unwrap() - .try_into() - .unwrap(); - - let res = router - .clone() - .oneshot(request) - .await - .unwrap() - .into_graphql_response_stream() - .await - .next() - .await - .unwrap() - .unwrap(); - assert!(res.data.is_some()); - assert!(res.errors.is_empty()); - - let s: Option = client.get(&format!("apq:{query_hash}")).await.unwrap(); - insta::assert_display_snapshot!(s.unwrap()); - - // we start a new router with the same config - // it should have the same connection to Redis, but the in memory cache has been reset - let router = apollo_router::TestHarness::builder() - .with_subgraph_network_requests() - .configuration_json(config.clone()) - .unwrap() - .schema(include_str!("../fixtures/supergraph.graphql")) - .build_router() - .await - .unwrap(); - - // a request with only the hash should succeed because it is stored in Redis - let request: router::Request = supergraph::Request::fake_builder() - .extension("persistedQuery", persisted.clone()) - .method(Method::POST) - .build() - .unwrap() - .try_into() - .unwrap(); - - let res = router - .clone() - .oneshot(request) - .await - .unwrap() - .into_graphql_response_stream() - .await - .next() - .await - .unwrap() - .unwrap(); - assert!(res.data.is_some()); - assert!(res.errors.is_empty()); - - client.quit().await.unwrap(); - // calling quit ends the connection and event listener tasks - let _ = connection_task.await; - Ok(()) - } - - #[tokio::test(flavor = "multi_thread")] - async fn entity_cache() -> Result<(), BoxError> { - let config = RedisConfig::from_url("redis://127.0.0.1:6379").unwrap(); - let client = RedisClient::new(config, None, None, None); - let connection_task = client.connect(); - client.wait_for_connect().await.unwrap(); - - let mut subgraphs = MockedSubgraphs::default(); - subgraphs.insert( - "products", - MockSubgraph::builder() - .with_json( - serde_json::json! {{"query":"{topProducts{__typename upc name}}"}}, - serde_json::json! {{"data": { - "topProducts": [{ - "__typename": "Product", - "upc": "1", - "name": "chair" - }, - { - "__typename": "Product", - "upc": "2", - "name": "table" - }] - }}}, - ) - .with_header(CACHE_CONTROL, HeaderValue::from_static("public")) - .build(), - ); - subgraphs.insert("reviews", MockSubgraph::builder().with_json( + })) + .unwrap() + .schema(include_str!("../fixtures/supergraph.graphql")) + .build_supergraph() + .await + .unwrap(); + + let request = supergraph::Request::fake_builder() + .query(r#"{ topProducts { name name2:name } }"#) + .method(Method::POST) + .build() + .unwrap(); + let _ = supergraph + .oneshot(request) + .await + .unwrap() + .next_response() + .await; + let new_exp: i64 = client + .custom_raw(cmd!("EXPIRETIME"), vec![known_cache_key.to_string()]) + .await + .and_then(|frame| frame.try_into()) + .and_then(|value: RedisValue| value.convert()) + .unwrap(); + + assert!(exp < new_exp); + + client.quit().await.unwrap(); + // calling quit ends the connection and event listener tasks + let _ = connection_task.await; + Ok(()) +} + +#[derive(Deserialize, Serialize)] + +struct QueryPlannerContent { + plan: QueryPlan, +} + +#[tokio::test(flavor = "multi_thread")] +async fn apq() -> Result<(), BoxError> { + let config = RedisConfig::from_url("redis://127.0.0.1:6379").unwrap(); + let client = RedisClient::new(config, None, None, None); + let connection_task = client.connect(); + client.wait_for_connect().await.unwrap(); + + let config = json!({ + "apq": { + "router": { + "cache": { + "in_memory": { + "limit": 2 + }, + "redis": { + "urls": ["redis://127.0.0.1:6379"], + "ttl": "10s" + } + } + } + } + }); + + let router = apollo_router::TestHarness::builder() + .with_subgraph_network_requests() + .configuration_json(config.clone()) + .unwrap() + .schema(include_str!("../fixtures/supergraph.graphql")) + .build_router() + .await + .unwrap(); + + let query_hash = "4c45433039407593557f8a982dafd316a66ec03f0e1ed5fa1b7ef8060d76e8ec"; + + client + .del::(&format!("apq:{query_hash}")) + .await + .unwrap(); + + let persisted = json!({ + "version" : 1, + "sha256Hash" : query_hash + }); + + // an APQ should fail if we do not know about the hash + // it should not set a value in Redis + let request: router::Request = supergraph::Request::fake_builder() + .extension("persistedQuery", persisted.clone()) + .method(Method::POST) + .build() + .unwrap() + .try_into() + .unwrap(); + + let res = router + .clone() + .oneshot(request) + .await + .unwrap() + .into_graphql_response_stream() + .await + .next() + .await + .unwrap() + .unwrap(); + assert_eq!( + res.errors.first().unwrap().message, + "PersistedQueryNotFound" + ); + let r: Option = client.get(&format!("apq:{query_hash}")).await.unwrap(); + assert!(r.is_none()); + + // Now we register the query + // it should set a value in Redis + let request: router::Request = supergraph::Request::fake_builder() + .query(r#"{ topProducts { name name2:name } }"#) + .extension("persistedQuery", persisted.clone()) + .method(Method::POST) + .build() + .unwrap() + .try_into() + .unwrap(); + + let res = router + .clone() + .oneshot(request) + .await + .unwrap() + .into_graphql_response_stream() + .await + .next() + .await + .unwrap() + .unwrap(); + assert!(res.data.is_some()); + assert!(res.errors.is_empty()); + + let s: Option = client.get(&format!("apq:{query_hash}")).await.unwrap(); + insta::assert_snapshot!(s.unwrap()); + + // we start a new router with the same config + // it should have the same connection to Redis, but the in memory cache has been reset + let router = apollo_router::TestHarness::builder() + .with_subgraph_network_requests() + .configuration_json(config.clone()) + .unwrap() + .schema(include_str!("../fixtures/supergraph.graphql")) + .build_router() + .await + .unwrap(); + + // a request with only the hash should succeed because it is stored in Redis + let request: router::Request = supergraph::Request::fake_builder() + .extension("persistedQuery", persisted.clone()) + .method(Method::POST) + .build() + .unwrap() + .try_into() + .unwrap(); + + let res = router + .clone() + .oneshot(request) + .await + .unwrap() + .into_graphql_response_stream() + .await + .next() + .await + .unwrap() + .unwrap(); + assert!(res.data.is_some()); + assert!(res.errors.is_empty()); + + client.quit().await.unwrap(); + // calling quit ends the connection and event listener tasks + let _ = connection_task.await; + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn entity_cache() -> Result<(), BoxError> { + let config = RedisConfig::from_url("redis://127.0.0.1:6379").unwrap(); + let client = RedisClient::new(config, None, None, None); + let connection_task = client.connect(); + client.wait_for_connect().await.unwrap(); + + let mut subgraphs = MockedSubgraphs::default(); + subgraphs.insert( + "products", + MockSubgraph::builder() + .with_json( + serde_json::json! {{"query":"{topProducts{__typename upc name}}"}}, + serde_json::json! {{"data": { + "topProducts": [{ + "__typename": "Product", + "upc": "1", + "name": "chair" + }, + { + "__typename": "Product", + "upc": "2", + "name": "table" + }] + }}}, + ) + .with_header(CACHE_CONTROL, HeaderValue::from_static("public")) + .build(), + ); + subgraphs.insert("reviews", MockSubgraph::builder().with_json( serde_json::json!{{ "query": "query($representations:[_Any!]!){_entities(representations:$representations){...on Product{reviews{body}}}}", "variables": { @@ -363,90 +363,90 @@ mod test { }}, ).with_header(CACHE_CONTROL, HeaderValue::from_static("public")).build()); - let supergraph = apollo_router::TestHarness::builder() - .with_subgraph_network_requests() - .configuration_json(json!({ - "preview_entity_cache": { - "redis": { - "urls": ["redis://127.0.0.1:6379"], - "ttl": "2s" + let supergraph = apollo_router::TestHarness::builder() + .with_subgraph_network_requests() + .configuration_json(json!({ + "preview_entity_cache": { + "redis": { + "urls": ["redis://127.0.0.1:6379"], + "ttl": "2s" + }, + "enabled": false, + "subgraphs": { + "products": { + "enabled": true, + "ttl": "60s" }, - "enabled": false, - "subgraphs": { - "products": { - "enabled": true, - "ttl": "60s" - }, - "reviews": { - "enabled": true, - "ttl": "10s" - } + "reviews": { + "enabled": true, + "ttl": "10s" } - }, - "include_subgraph_errors": { - "all": true } - })) - .unwrap() - .extra_plugin(subgraphs) - .schema(include_str!("../fixtures/supergraph-auth.graphql")) - .build_supergraph() - .await - .unwrap(); - - let request = supergraph::Request::fake_builder() - .query(r#"{ topProducts { name reviews { body } } }"#) - .method(Method::POST) - .build() - .unwrap(); - - let response = supergraph - .oneshot(request) - .await - .unwrap() - .next_response() - .await - .unwrap(); - insta::assert_json_snapshot!(response); - - let s:String = client + }, + "include_subgraph_errors": { + "all": true + } + })) + .unwrap() + .extra_plugin(subgraphs) + .schema(include_str!("../fixtures/supergraph-auth.graphql")) + .build_supergraph() + .await + .unwrap(); + + let request = supergraph::Request::fake_builder() + .query(r#"{ topProducts { name reviews { body } } }"#) + .method(Method::POST) + .build() + .unwrap(); + + let response = supergraph + .oneshot(request) + .await + .unwrap() + .next_response() + .await + .unwrap(); + insta::assert_json_snapshot!(response); + + let s:String = client .get("subgraph:products:Query:0df945dc1bc08f7fc02e8905b4c72aa9112f29bb7a214e4a38d199f0aa635b48:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c") .await .unwrap(); - let v: Value = serde_json::from_str(&s).unwrap(); - insta::assert_json_snapshot!(v.as_object().unwrap().get("data").unwrap()); - - let s: String = client.get("subgraph:reviews:Product:4911f7a9dbad8a47b8900d65547503a2f3c0359f65c0bc5652ad9b9843281f66:1de543dab57fde0f00247922ccc4f76d4c916ae26a89dd83cd1a62300d0cda20:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c").await.unwrap(); - let v: Value = serde_json::from_str(&s).unwrap(); - insta::assert_json_snapshot!(v.as_object().unwrap().get("data").unwrap()); - - // we abuse the query shape to return a response with a different but overlapping set of entities - let mut subgraphs = MockedSubgraphs::default(); - subgraphs.insert( - "products", - MockSubgraph::builder() - .with_json( - serde_json::json! {{"query":"{topProducts(first:2){__typename upc name}}"}}, - serde_json::json! {{"data": { - "topProducts": [{ - "__typename": "Product", - "upc": "1", - "name": "chair" - }, - { - "__typename": "Product", - "upc": "3", - "name": "plate" - }] - }}}, - ) - .with_header(CACHE_CONTROL, HeaderValue::from_static("public")) - .build(), - ); + let v: Value = serde_json::from_str(&s).unwrap(); + insta::assert_json_snapshot!(v.as_object().unwrap().get("data").unwrap()); + + let s: String = client.get("subgraph:reviews:Product:4911f7a9dbad8a47b8900d65547503a2f3c0359f65c0bc5652ad9b9843281f66:1de543dab57fde0f00247922ccc4f76d4c916ae26a89dd83cd1a62300d0cda20:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c").await.unwrap(); + let v: Value = serde_json::from_str(&s).unwrap(); + insta::assert_json_snapshot!(v.as_object().unwrap().get("data").unwrap()); + + // we abuse the query shape to return a response with a different but overlapping set of entities + let mut subgraphs = MockedSubgraphs::default(); + subgraphs.insert( + "products", + MockSubgraph::builder() + .with_json( + serde_json::json! {{"query":"{topProducts(first:2){__typename upc name}}"}}, + serde_json::json! {{"data": { + "topProducts": [{ + "__typename": "Product", + "upc": "1", + "name": "chair" + }, + { + "__typename": "Product", + "upc": "3", + "name": "plate" + }] + }}}, + ) + .with_header(CACHE_CONTROL, HeaderValue::from_static("public")) + .build(), + ); - // even though the root operation returned 2 entities, we only need to get one entity from the subgraph here because - // we already have it in cache - subgraphs.insert("reviews", MockSubgraph::builder().with_json( + // even though the root operation returned 2 entities, we only need to get one entity from the subgraph here because + // we already have it in cache + subgraphs.insert("reviews", MockSubgraph::builder().with_json( serde_json::json!{{ "query": "query($representations:[_Any!]!){_entities(representations:$representations){...on Product{reviews{body}}}}", "variables": { @@ -468,74 +468,74 @@ mod test { }}, ).with_header(CACHE_CONTROL, HeaderValue::from_static("public")).build()); - let supergraph = apollo_router::TestHarness::builder() - .with_subgraph_network_requests() - .configuration_json(json!({ - "preview_entity_cache": { - "redis": { - "urls": ["redis://127.0.0.1:6379"], - "ttl": "2s" + let supergraph = apollo_router::TestHarness::builder() + .with_subgraph_network_requests() + .configuration_json(json!({ + "preview_entity_cache": { + "redis": { + "urls": ["redis://127.0.0.1:6379"], + "ttl": "2s" + }, + "enabled": false, + "subgraphs": { + "products": { + "enabled": true, + "ttl": "60s" }, - "enabled": false, - "subgraphs": { - "products": { - "enabled": true, - "ttl": "60s" - }, - "reviews": { - "enabled": true, - "ttl": "10s" - } + "reviews": { + "enabled": true, + "ttl": "10s" } - }, - "include_subgraph_errors": { - "all": true } - })) - .unwrap() - .extra_plugin(subgraphs) - .schema(include_str!("../fixtures/supergraph-auth.graphql")) - .build_supergraph() - .await - .unwrap(); - - let request = supergraph::Request::fake_builder() - .query(r#"{ topProducts(first: 2) { name reviews { body } } }"#) - .method(Method::POST) - .build() - .unwrap(); - - let response = supergraph - .oneshot(request) - .await - .unwrap() - .next_response() - .await - .unwrap(); - insta::assert_json_snapshot!(response); - - let s:String = client + }, + "include_subgraph_errors": { + "all": true + } + })) + .unwrap() + .extra_plugin(subgraphs) + .schema(include_str!("../fixtures/supergraph-auth.graphql")) + .build_supergraph() + .await + .unwrap(); + + let request = supergraph::Request::fake_builder() + .query(r#"{ topProducts(first: 2) { name reviews { body } } }"#) + .method(Method::POST) + .build() + .unwrap(); + + let response = supergraph + .oneshot(request) + .await + .unwrap() + .next_response() + .await + .unwrap(); + insta::assert_json_snapshot!(response); + + let s:String = client .get("subgraph:reviews:Product:d9a4cd73308dd13ca136390c10340823f94c335b9da198d2339c886c738abf0d:1de543dab57fde0f00247922ccc4f76d4c916ae26a89dd83cd1a62300d0cda20:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c") .await .unwrap(); - let v: Value = serde_json::from_str(&s).unwrap(); - insta::assert_json_snapshot!(v.as_object().unwrap().get("data").unwrap()); + let v: Value = serde_json::from_str(&s).unwrap(); + insta::assert_json_snapshot!(v.as_object().unwrap().get("data").unwrap()); - client.quit().await.unwrap(); - // calling quit ends the connection and event listener tasks - let _ = connection_task.await; - Ok(()) - } + client.quit().await.unwrap(); + // calling quit ends the connection and event listener tasks + let _ = connection_task.await; + Ok(()) +} - #[tokio::test(flavor = "multi_thread")] - async fn entity_cache_authorization() -> Result<(), BoxError> { - let config = RedisConfig::from_url("redis://127.0.0.1:6379").unwrap(); - let client = RedisClient::new(config, None, None, None); - let connection_task = client.connect(); - client.wait_for_connect().await.unwrap(); +#[tokio::test(flavor = "multi_thread")] +async fn entity_cache_authorization() -> Result<(), BoxError> { + let config = RedisConfig::from_url("redis://127.0.0.1:6379").unwrap(); + let client = RedisClient::new(config, None, None, None); + let connection_task = client.connect(); + client.wait_for_connect().await.unwrap(); - let mut subgraphs = MockedSubgraphs::default(); - subgraphs.insert( + let mut subgraphs = MockedSubgraphs::default(); + subgraphs.insert( "accounts", MockSubgraph::builder().with_json( serde_json::json!{{ @@ -569,28 +569,28 @@ mod test { ).with_header(CACHE_CONTROL, HeaderValue::from_static("public")) .build(), ); - subgraphs.insert( - "products", - MockSubgraph::builder() - .with_json( - serde_json::json! {{"query":"{topProducts{__typename upc name}}"}}, - serde_json::json! {{"data": { - "topProducts": [{ - "__typename": "Product", - "upc": "1", - "name": "chair" - }, - { - "__typename": "Product", - "upc": "2", - "name": "table" - }] - }}}, - ) - .with_header(CACHE_CONTROL, HeaderValue::from_static("public")) - .build(), - ); - subgraphs.insert( + subgraphs.insert( + "products", + MockSubgraph::builder() + .with_json( + serde_json::json! {{"query":"{topProducts{__typename upc name}}"}}, + serde_json::json! {{"data": { + "topProducts": [{ + "__typename": "Product", + "upc": "1", + "name": "chair" + }, + { + "__typename": "Product", + "upc": "2", + "name": "table" + }] + }}}, + ) + .with_header(CACHE_CONTROL, HeaderValue::from_static("public")) + .build(), + ); + subgraphs.insert( "reviews", MockSubgraph::builder().with_json( serde_json::json!{{ @@ -666,239 +666,319 @@ mod test { .build(), ); - let supergraph = apollo_router::TestHarness::builder() - .with_subgraph_network_requests() - .configuration_json(json!({ - "preview_entity_cache": { - "redis": { - "urls": ["redis://127.0.0.1:6379"], - "ttl": "2s" - }, - "enabled": false, - "subgraphs": { - "products": { - "enabled": true, - "ttl": "60s" - }, - "reviews": { - "enabled": true, - "ttl": "10s" - } - } + let supergraph = apollo_router::TestHarness::builder() + .with_subgraph_network_requests() + .configuration_json(json!({ + "preview_entity_cache": { + "redis": { + "urls": ["redis://127.0.0.1:6379"], + "ttl": "2s" }, - "authorization": { - "preview_directives": { - "enabled": true + "enabled": false, + "subgraphs": { + "products": { + "enabled": true, + "ttl": "60s" + }, + "reviews": { + "enabled": true, + "ttl": "10s" } - }, - "include_subgraph_errors": { - "all": true } - })) - .unwrap() - .extra_plugin(subgraphs) - .schema(include_str!("../fixtures/supergraph-auth.graphql")) - .build_supergraph() - .await - .unwrap(); - - let context = Context::new(); - context - .insert( - "apollo_authorization::scopes::required", - json! {["profile", "read:user", "read:name"]}, - ) - .unwrap(); - let request = supergraph::Request::fake_builder() - .query( - r#"{ me { id name } topProducts { name reviews { body author { username } } } }"#, - ) - .context(context) - .method(Method::POST) - .build() - .unwrap(); - - let response = supergraph - .clone() - .oneshot(request) - .await - .unwrap() - .next_response() - .await - .unwrap(); - insta::assert_json_snapshot!(response); - - let s:String = client + }, + "authorization": { + "preview_directives": { + "enabled": true + } + }, + "include_subgraph_errors": { + "all": true + } + })) + .unwrap() + .extra_plugin(subgraphs) + .schema(include_str!("../fixtures/supergraph-auth.graphql")) + .build_supergraph() + .await + .unwrap(); + + let context = Context::new(); + context + .insert( + "apollo_authorization::scopes::required", + json! {["profile", "read:user", "read:name"]}, + ) + .unwrap(); + let request = supergraph::Request::fake_builder() + .query(r#"{ me { id name } topProducts { name reviews { body author { username } } } }"#) + .context(context) + .method(Method::POST) + .build() + .unwrap(); + + let response = supergraph + .clone() + .oneshot(request) + .await + .unwrap() + .next_response() + .await + .unwrap(); + insta::assert_json_snapshot!(response); + + let s:String = client .get("subgraph:products:Query:0df945dc1bc08f7fc02e8905b4c72aa9112f29bb7a214e4a38d199f0aa635b48:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c") .await .unwrap(); - let v: Value = serde_json::from_str(&s).unwrap(); - assert_eq!( - v.as_object().unwrap().get("data").unwrap(), - &json! {{ - "topProducts": [{ - "__typename": "Product", - "upc": "1", - "name": "chair" - }, - { - "__typename": "Product", - "upc": "2", - "name": "table" - }] - }} - ); - - let s: String = client + let v: Value = serde_json::from_str(&s).unwrap(); + assert_eq!( + v.as_object().unwrap().get("data").unwrap(), + &json! {{ + "topProducts": [{ + "__typename": "Product", + "upc": "1", + "name": "chair" + }, + { + "__typename": "Product", + "upc": "2", + "name": "table" + }] + }} + ); + + let s: String = client .get("subgraph:reviews:Product:4911f7a9dbad8a47b8900d65547503a2f3c0359f65c0bc5652ad9b9843281f66:1de543dab57fde0f00247922ccc4f76d4c916ae26a89dd83cd1a62300d0cda20:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c") .await .unwrap(); - let v: Value = serde_json::from_str(&s).unwrap(); - assert_eq!( - v.as_object().unwrap().get("data").unwrap(), - &json! {{ - "reviews": [ - {"body": "I can sit on it"} - ] - }} - ); + let v: Value = serde_json::from_str(&s).unwrap(); + assert_eq!( + v.as_object().unwrap().get("data").unwrap(), + &json! {{ + "reviews": [ + {"body": "I can sit on it"} + ] + }} + ); + + let context = Context::new(); + context + .insert( + "apollo_authorization::scopes::required", + json! {["profile", "read:user", "read:name"]}, + ) + .unwrap(); + context + .insert( + "apollo_authentication::JWT::claims", + json! {{ "scope": "read:user read:name" }}, + ) + .unwrap(); + let request = supergraph::Request::fake_builder() + .query(r#"{ me { id name } topProducts { name reviews { body author { username } } } }"#) + .context(context) + .method(Method::POST) + .build() + .unwrap(); - let context = Context::new(); - context - .insert( - "apollo_authorization::scopes::required", - json! {["profile", "read:user", "read:name"]}, - ) - .unwrap(); - context - .insert( - "apollo_authentication::JWT::claims", - json! {{ "scope": "read:user read:name" }}, - ) - .unwrap(); - let request = supergraph::Request::fake_builder() - .query( - r#"{ me { id name } topProducts { name reviews { body author { username } } } }"#, - ) - .context(context) - .method(Method::POST) - .build() - .unwrap(); - - let response = supergraph - .clone() - .oneshot(request) - .await - .unwrap() - .next_response() - .await - .unwrap(); - insta::assert_json_snapshot!(response); - - let s:String = client + let response = supergraph + .clone() + .oneshot(request) + .await + .unwrap() + .next_response() + .await + .unwrap(); + insta::assert_json_snapshot!(response); + + let s:String = client .get("subgraph:reviews:Product:4911f7a9dbad8a47b8900d65547503a2f3c0359f65c0bc5652ad9b9843281f66:3b6ef3c8fd34c469d59f513942c5f4c8f91135e828712de2024e2cd4613c50ae:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c") .await .unwrap(); - let v: Value = serde_json::from_str(&s).unwrap(); - assert_eq!( - v.as_object().unwrap().get("data").unwrap(), - &json! {{ - "reviews": [{ - "body": "I can sit on it", - "author": {"__typename": "User", "id": "1"} - }] - }} - ); + let v: Value = serde_json::from_str(&s).unwrap(); + assert_eq!( + v.as_object().unwrap().get("data").unwrap(), + &json! {{ + "reviews": [{ + "body": "I can sit on it", + "author": {"__typename": "User", "id": "1"} + }] + }} + ); + + let context = Context::new(); + context + .insert( + "apollo_authorization::scopes::required", + json! {["profile", "read:user", "read:name"]}, + ) + .unwrap(); + context + .insert( + "apollo_authentication::JWT::claims", + json! {{ "scope": "read:user profile" }}, + ) + .unwrap(); + let request = supergraph::Request::fake_builder() + .query(r#"{ me { id name } topProducts { name reviews { body author { username } } } }"#) + .context(context) + .method(Method::POST) + .build() + .unwrap(); - let context = Context::new(); - context - .insert( - "apollo_authorization::scopes::required", - json! {["profile", "read:user", "read:name"]}, - ) - .unwrap(); - context - .insert( - "apollo_authentication::JWT::claims", - json! {{ "scope": "read:user profile" }}, - ) - .unwrap(); - let request = supergraph::Request::fake_builder() - .query( - r#"{ me { id name } topProducts { name reviews { body author { username } } } }"#, - ) - .context(context) - .method(Method::POST) - .build() - .unwrap(); - - let response = supergraph - .clone() - .oneshot(request) - .await - .unwrap() - .next_response() - .await - .unwrap(); - insta::assert_json_snapshot!(response); - - client.quit().await.unwrap(); - // calling quit ends the connection and event listener tasks - let _ = connection_task.await; - Ok(()) - } - - #[tokio::test(flavor = "multi_thread")] - async fn connection_failure_blocks_startup() { - let _ = apollo_router::TestHarness::builder() - .with_subgraph_network_requests() - .configuration_json(json!({ - "supergraph": { - "query_planning": { - "cache": { - "in_memory": { - "limit": 2 - }, - "redis": { - // invalid port - "urls": ["redis://127.0.0.1:6378"] - } + let response = supergraph + .clone() + .oneshot(request) + .await + .unwrap() + .next_response() + .await + .unwrap(); + insta::assert_json_snapshot!(response); + + client.quit().await.unwrap(); + // calling quit ends the connection and event listener tasks + let _ = connection_task.await; + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn connection_failure_blocks_startup() { + let _ = apollo_router::TestHarness::builder() + .with_subgraph_network_requests() + .configuration_json(json!({ + "supergraph": { + "query_planning": { + "cache": { + "in_memory": { + "limit": 2 + }, + "redis": { + // invalid port + "urls": ["redis://127.0.0.1:6378"] } } } - })) - .unwrap() - .schema(include_str!("../fixtures/supergraph.graphql")) - .build_supergraph() - .await - .unwrap(); - - let e = apollo_router::TestHarness::builder() - .with_subgraph_network_requests() - .configuration_json(json!({ - "supergraph": { - "query_planning": { - "cache": { - "in_memory": { - "limit": 2 - }, - "redis": { - // invalid port - "urls": ["redis://127.0.0.1:6378"], - "required_to_start": true - } + } + })) + .unwrap() + .schema(include_str!("../fixtures/supergraph.graphql")) + .build_supergraph() + .await + .unwrap(); + + let e = apollo_router::TestHarness::builder() + .with_subgraph_network_requests() + .configuration_json(json!({ + "supergraph": { + "query_planning": { + "cache": { + "in_memory": { + "limit": 2 + }, + "redis": { + // invalid port + "urls": ["redis://127.0.0.1:6378"], + "required_to_start": true } } } - })) - .unwrap() - .schema(include_str!("../fixtures/supergraph.graphql")) - .build_supergraph() - .await - .unwrap_err(); - assert_eq!( - e.to_string(), + } + })) + .unwrap() + .schema(include_str!("../fixtures/supergraph.graphql")) + .build_supergraph() + .await + .unwrap_err(); + //OSX has a different error code for connection refused + let e = e.to_string().replace("61", "111"); // + assert_eq!( + e, "couldn't build Router service: IO Error: Os { code: 111, kind: ConnectionRefused, message: \"Connection refused\" }" ); - } +} + +#[tokio::test(flavor = "multi_thread")] +async fn query_planner_redis_update_query_fragments() { + test_redis_query_plan_config_update( + include_str!("fixtures/query_planner_redis_config_update_query_fragments.router.yaml"), + "plan:0:v2.7.5:a9e605fa09adc5a4b824e690b4de6f160d47d84ede5956b58a7d300cca1f7204:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:ae8b525534cb7446a34715fc80edd41d4d29aa65c5f39f9237d4ed8459e3fe82", + ) + .await; +} + +#[tokio::test(flavor = "multi_thread")] +#[ignore = "extraction of subgraphs from supergraph is not yet implemented"] +async fn query_planner_redis_update_planner_mode() { + test_redis_query_plan_config_update( + include_str!("fixtures/query_planner_redis_config_update_query_planner_mode.router.yaml"), + "", + ) + .await; +} + +#[tokio::test(flavor = "multi_thread")] +async fn query_planner_redis_update_introspection() { + test_redis_query_plan_config_update( + include_str!("fixtures/query_planner_redis_config_update_introspection.router.yaml"), + "plan:0:v2.7.5:a9e605fa09adc5a4b824e690b4de6f160d47d84ede5956b58a7d300cca1f7204:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:1910d63916aae7a1066cb8c7d622fc3a8e363ed1b6ac8e214deed4046abae85c", + ) + .await; +} + +#[tokio::test(flavor = "multi_thread")] +async fn query_planner_redis_update_defer() { + test_redis_query_plan_config_update( + include_str!("fixtures/query_planner_redis_config_update_defer.router.yaml"), + "plan:0:v2.7.5:a9e605fa09adc5a4b824e690b4de6f160d47d84ede5956b58a7d300cca1f7204:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:8a17c5b196af5e3a18d24596424e9849d198f456dd48297b852a5f2ca847169b", + ) + .await; +} + +#[tokio::test(flavor = "multi_thread")] +async fn query_planner_redis_update_type_conditional_fetching() { + test_redis_query_plan_config_update( + include_str!( + "fixtures/query_planner_redis_config_update_type_conditional_fetching.router.yaml" + ), + "plan:0:v2.7.5:a9e605fa09adc5a4b824e690b4de6f160d47d84ede5956b58a7d300cca1f7204:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:275f78612ed3d45cdf6bf328ef83e368b5a44393bd8c944d4a7d694aed61f017", + ) + .await; +} + +#[tokio::test(flavor = "multi_thread")] +async fn query_planner_redis_update_reuse_query_fragments() { + test_redis_query_plan_config_update( + include_str!( + "fixtures/query_planner_redis_config_update_reuse_query_fragments.router.yaml" + ), + "plan:0:v2.7.5:a9e605fa09adc5a4b824e690b4de6f160d47d84ede5956b58a7d300cca1f7204:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:15fbb62c94e8da6ea78f28a6eb86a615dcaf27ff6fd0748fac4eb614b0b17662", + ) + .await; +} + +async fn test_redis_query_plan_config_update(updated_config: &str, new_cache_key: &str) { + // This test shows that the redis key changes when the query planner config changes. + // The test starts a router with a specific config, executes a query, and checks the redis cache key. + // Then it updates the config, executes the query again, and checks the redis cache key. + let mut router = IntegrationTest::builder() + .config(include_str!( + "fixtures/query_planner_redis_config_update.router.yaml" + )) + .build() + .await; + + router.start().await; + router.assert_started().await; + router.clear_redis_cache().await; + + let starting_key = "plan:0:v2.7.5:a9e605fa09adc5a4b824e690b4de6f160d47d84ede5956b58a7d300cca1f7204:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:1910d63916aae7a1066cb8c7d622fc3a8e363ed1b6ac8e214deed4046abae85c"; + router.execute_default_query().await; + router.assert_redis_cache_contains(starting_key, None).await; + router.update_config(updated_config).await; + router.assert_reloaded().await; + router.execute_default_query().await; + router + .assert_redis_cache_contains(new_cache_key, Some(starting_key)) + .await; } diff --git a/apollo-router/tests/integration/snapshots/integration_tests__integration__coprocessor__error_not_propagated_to_client.snap b/apollo-router/tests/integration/snapshots/integration_tests__integration__coprocessor__error_not_propagated_to_client.snap new file mode 100644 index 0000000000..c3fcb68742 --- /dev/null +++ b/apollo-router/tests/integration/snapshots/integration_tests__integration__coprocessor__error_not_propagated_to_client.snap @@ -0,0 +1,5 @@ +--- +source: apollo-router/tests/integration/coprocessor.rs +expression: response.text().await? +--- +"{\"errors\":[{\"message\":\"internal server error\",\"extensions\":{\"code\":\"INTERNAL_SERVER_ERROR\"}}]}" diff --git a/apollo-router/tests/integration/snapshots/integration_tests__integration__redis__test__apq.snap b/apollo-router/tests/integration/snapshots/integration_tests__integration__redis__apq.snap similarity index 100% rename from apollo-router/tests/integration/snapshots/integration_tests__integration__redis__test__apq.snap rename to apollo-router/tests/integration/snapshots/integration_tests__integration__redis__apq.snap diff --git a/apollo-router/tests/integration/snapshots/integration_tests__integration__redis__test__entity_cache-2.snap b/apollo-router/tests/integration/snapshots/integration_tests__integration__redis__entity_cache-2.snap similarity index 100% rename from apollo-router/tests/integration/snapshots/integration_tests__integration__redis__test__entity_cache-2.snap rename to apollo-router/tests/integration/snapshots/integration_tests__integration__redis__entity_cache-2.snap diff --git a/apollo-router/tests/integration/snapshots/integration_tests__integration__redis__test__entity_cache-3.snap b/apollo-router/tests/integration/snapshots/integration_tests__integration__redis__entity_cache-3.snap similarity index 100% rename from apollo-router/tests/integration/snapshots/integration_tests__integration__redis__test__entity_cache-3.snap rename to apollo-router/tests/integration/snapshots/integration_tests__integration__redis__entity_cache-3.snap diff --git a/apollo-router/tests/integration/snapshots/integration_tests__integration__redis__test__entity_cache-4.snap b/apollo-router/tests/integration/snapshots/integration_tests__integration__redis__entity_cache-4.snap similarity index 100% rename from apollo-router/tests/integration/snapshots/integration_tests__integration__redis__test__entity_cache-4.snap rename to apollo-router/tests/integration/snapshots/integration_tests__integration__redis__entity_cache-4.snap diff --git a/apollo-router/tests/integration/snapshots/integration_tests__integration__redis__test__entity_cache-5.snap b/apollo-router/tests/integration/snapshots/integration_tests__integration__redis__entity_cache-5.snap similarity index 100% rename from apollo-router/tests/integration/snapshots/integration_tests__integration__redis__test__entity_cache-5.snap rename to apollo-router/tests/integration/snapshots/integration_tests__integration__redis__entity_cache-5.snap diff --git a/apollo-router/tests/integration/snapshots/integration_tests__integration__redis__test__entity_cache.snap b/apollo-router/tests/integration/snapshots/integration_tests__integration__redis__entity_cache.snap similarity index 100% rename from apollo-router/tests/integration/snapshots/integration_tests__integration__redis__test__entity_cache.snap rename to apollo-router/tests/integration/snapshots/integration_tests__integration__redis__entity_cache.snap diff --git a/apollo-router/tests/integration/snapshots/integration_tests__integration__redis__test__entity_cache_authorization-2.snap b/apollo-router/tests/integration/snapshots/integration_tests__integration__redis__entity_cache_authorization-2.snap similarity index 100% rename from apollo-router/tests/integration/snapshots/integration_tests__integration__redis__test__entity_cache_authorization-2.snap rename to apollo-router/tests/integration/snapshots/integration_tests__integration__redis__entity_cache_authorization-2.snap diff --git a/apollo-router/tests/integration/snapshots/integration_tests__integration__redis__test__entity_cache_authorization-3.snap b/apollo-router/tests/integration/snapshots/integration_tests__integration__redis__entity_cache_authorization-3.snap similarity index 100% rename from apollo-router/tests/integration/snapshots/integration_tests__integration__redis__test__entity_cache_authorization-3.snap rename to apollo-router/tests/integration/snapshots/integration_tests__integration__redis__entity_cache_authorization-3.snap diff --git a/apollo-router/tests/integration/snapshots/integration_tests__integration__redis__test__entity_cache_authorization.snap b/apollo-router/tests/integration/snapshots/integration_tests__integration__redis__entity_cache_authorization.snap similarity index 100% rename from apollo-router/tests/integration/snapshots/integration_tests__integration__redis__test__entity_cache_authorization.snap rename to apollo-router/tests/integration/snapshots/integration_tests__integration__redis__entity_cache_authorization.snap diff --git a/apollo-router/tests/integration/snapshots/integration_tests__integration__redis__test__query_planner.snap b/apollo-router/tests/integration/snapshots/integration_tests__integration__redis__query_planner.snap similarity index 100% rename from apollo-router/tests/integration/snapshots/integration_tests__integration__redis__test__query_planner.snap rename to apollo-router/tests/integration/snapshots/integration_tests__integration__redis__query_planner.snap diff --git a/apollo-router/tests/snapshots/apollo_reports__batch_send_header-2.snap b/apollo-router/tests/snapshots/apollo_reports__batch_send_header-2.snap index 8c72378643..fb800a2d1e 100644 --- a/apollo-router/tests/snapshots/apollo_reports__batch_send_header-2.snap +++ b/apollo-router/tests/snapshots/apollo_reports__batch_send_header-2.snap @@ -186,6 +186,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -429,6 +430,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -536,6 +538,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -549,6 +552,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ - start_time: seconds: "[seconds]" nanos: "[nanos]" @@ -722,6 +726,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -965,6 +970,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -1072,6 +1078,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -1085,6 +1092,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ stats_with_context: [] referenced_fields_by_type: {} query_metadata: ~ diff --git a/apollo-router/tests/snapshots/apollo_reports__batch_send_header.snap b/apollo-router/tests/snapshots/apollo_reports__batch_send_header.snap index 8c72378643..fb800a2d1e 100644 --- a/apollo-router/tests/snapshots/apollo_reports__batch_send_header.snap +++ b/apollo-router/tests/snapshots/apollo_reports__batch_send_header.snap @@ -186,6 +186,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -429,6 +430,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -536,6 +538,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -549,6 +552,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ - start_time: seconds: "[seconds]" nanos: "[nanos]" @@ -722,6 +726,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -965,6 +970,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -1072,6 +1078,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -1085,6 +1092,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ stats_with_context: [] referenced_fields_by_type: {} query_metadata: ~ diff --git a/apollo-router/tests/snapshots/apollo_reports__batch_trace_id-2.snap b/apollo-router/tests/snapshots/apollo_reports__batch_trace_id-2.snap index 9d49165680..f79e977f99 100644 --- a/apollo-router/tests/snapshots/apollo_reports__batch_trace_id-2.snap +++ b/apollo-router/tests/snapshots/apollo_reports__batch_trace_id-2.snap @@ -183,6 +183,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -426,6 +427,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -533,6 +535,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -546,6 +549,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ - start_time: seconds: "[seconds]" nanos: "[nanos]" @@ -716,6 +720,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -959,6 +964,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -1066,6 +1072,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -1079,6 +1086,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ stats_with_context: [] referenced_fields_by_type: {} query_metadata: ~ diff --git a/apollo-router/tests/snapshots/apollo_reports__batch_trace_id.snap b/apollo-router/tests/snapshots/apollo_reports__batch_trace_id.snap index 9d49165680..f79e977f99 100644 --- a/apollo-router/tests/snapshots/apollo_reports__batch_trace_id.snap +++ b/apollo-router/tests/snapshots/apollo_reports__batch_trace_id.snap @@ -183,6 +183,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -426,6 +427,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -533,6 +535,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -546,6 +549,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ - start_time: seconds: "[seconds]" nanos: "[nanos]" @@ -716,6 +720,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -959,6 +964,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -1066,6 +1072,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -1079,6 +1086,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ stats_with_context: [] referenced_fields_by_type: {} query_metadata: ~ diff --git a/apollo-router/tests/snapshots/apollo_reports__client_name-2.snap b/apollo-router/tests/snapshots/apollo_reports__client_name-2.snap index 05f1eb4350..b1461034c3 100644 --- a/apollo-router/tests/snapshots/apollo_reports__client_name-2.snap +++ b/apollo-router/tests/snapshots/apollo_reports__client_name-2.snap @@ -183,6 +183,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -426,6 +427,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -533,6 +535,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -546,6 +549,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ stats_with_context: [] referenced_fields_by_type: {} query_metadata: ~ diff --git a/apollo-router/tests/snapshots/apollo_reports__client_name.snap b/apollo-router/tests/snapshots/apollo_reports__client_name.snap index 05f1eb4350..b1461034c3 100644 --- a/apollo-router/tests/snapshots/apollo_reports__client_name.snap +++ b/apollo-router/tests/snapshots/apollo_reports__client_name.snap @@ -183,6 +183,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -426,6 +427,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -533,6 +535,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -546,6 +549,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ stats_with_context: [] referenced_fields_by_type: {} query_metadata: ~ diff --git a/apollo-router/tests/snapshots/apollo_reports__client_version-2.snap b/apollo-router/tests/snapshots/apollo_reports__client_version-2.snap index 8b44f27880..5fc4275f42 100644 --- a/apollo-router/tests/snapshots/apollo_reports__client_version-2.snap +++ b/apollo-router/tests/snapshots/apollo_reports__client_version-2.snap @@ -183,6 +183,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -426,6 +427,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -533,6 +535,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -546,6 +549,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ stats_with_context: [] referenced_fields_by_type: {} query_metadata: ~ diff --git a/apollo-router/tests/snapshots/apollo_reports__client_version.snap b/apollo-router/tests/snapshots/apollo_reports__client_version.snap index 8b44f27880..5fc4275f42 100644 --- a/apollo-router/tests/snapshots/apollo_reports__client_version.snap +++ b/apollo-router/tests/snapshots/apollo_reports__client_version.snap @@ -183,6 +183,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -426,6 +427,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -533,6 +535,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -546,6 +549,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ stats_with_context: [] referenced_fields_by_type: {} query_metadata: ~ diff --git a/apollo-router/tests/snapshots/apollo_reports__condition_else-2.snap b/apollo-router/tests/snapshots/apollo_reports__condition_else-2.snap index 10b01ce754..4d5c99b83d 100644 --- a/apollo-router/tests/snapshots/apollo_reports__condition_else-2.snap +++ b/apollo-router/tests/snapshots/apollo_reports__condition_else-2.snap @@ -189,6 +189,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -432,6 +433,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -539,6 +541,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -552,6 +555,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ stats_with_context: [] referenced_fields_by_type: {} query_metadata: ~ diff --git a/apollo-router/tests/snapshots/apollo_reports__condition_else.snap b/apollo-router/tests/snapshots/apollo_reports__condition_else.snap index 10b01ce754..4d5c99b83d 100644 --- a/apollo-router/tests/snapshots/apollo_reports__condition_else.snap +++ b/apollo-router/tests/snapshots/apollo_reports__condition_else.snap @@ -189,6 +189,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -432,6 +433,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -539,6 +541,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -552,6 +555,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ stats_with_context: [] referenced_fields_by_type: {} query_metadata: ~ diff --git a/apollo-router/tests/snapshots/apollo_reports__condition_if-2.snap b/apollo-router/tests/snapshots/apollo_reports__condition_if-2.snap index 71754c71bc..ad368c8265 100644 --- a/apollo-router/tests/snapshots/apollo_reports__condition_if-2.snap +++ b/apollo-router/tests/snapshots/apollo_reports__condition_if-2.snap @@ -189,6 +189,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -444,6 +445,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -551,6 +553,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -565,6 +568,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ stats_with_context: [] referenced_fields_by_type: {} query_metadata: ~ diff --git a/apollo-router/tests/snapshots/apollo_reports__condition_if.snap b/apollo-router/tests/snapshots/apollo_reports__condition_if.snap index 71754c71bc..ad368c8265 100644 --- a/apollo-router/tests/snapshots/apollo_reports__condition_if.snap +++ b/apollo-router/tests/snapshots/apollo_reports__condition_if.snap @@ -189,6 +189,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -444,6 +445,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -551,6 +553,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -565,6 +568,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ stats_with_context: [] referenced_fields_by_type: {} query_metadata: ~ diff --git a/apollo-router/tests/snapshots/apollo_reports__non_defer-2.snap b/apollo-router/tests/snapshots/apollo_reports__non_defer-2.snap index 140015c24b..3cbf105e67 100644 --- a/apollo-router/tests/snapshots/apollo_reports__non_defer-2.snap +++ b/apollo-router/tests/snapshots/apollo_reports__non_defer-2.snap @@ -183,6 +183,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -426,6 +427,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -533,6 +535,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -546,6 +549,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ stats_with_context: [] referenced_fields_by_type: {} query_metadata: ~ diff --git a/apollo-router/tests/snapshots/apollo_reports__non_defer.snap b/apollo-router/tests/snapshots/apollo_reports__non_defer.snap index 140015c24b..3cbf105e67 100644 --- a/apollo-router/tests/snapshots/apollo_reports__non_defer.snap +++ b/apollo-router/tests/snapshots/apollo_reports__non_defer.snap @@ -183,6 +183,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -426,6 +427,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -533,6 +535,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -546,6 +549,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ stats_with_context: [] referenced_fields_by_type: {} query_metadata: ~ diff --git a/apollo-router/tests/snapshots/apollo_reports__send_header-2.snap b/apollo-router/tests/snapshots/apollo_reports__send_header-2.snap index 913116e1f5..aec0e1e443 100644 --- a/apollo-router/tests/snapshots/apollo_reports__send_header-2.snap +++ b/apollo-router/tests/snapshots/apollo_reports__send_header-2.snap @@ -186,6 +186,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -429,6 +430,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -536,6 +538,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -549,6 +552,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ stats_with_context: [] referenced_fields_by_type: {} query_metadata: ~ diff --git a/apollo-router/tests/snapshots/apollo_reports__send_header.snap b/apollo-router/tests/snapshots/apollo_reports__send_header.snap index 913116e1f5..aec0e1e443 100644 --- a/apollo-router/tests/snapshots/apollo_reports__send_header.snap +++ b/apollo-router/tests/snapshots/apollo_reports__send_header.snap @@ -186,6 +186,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -429,6 +430,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -536,6 +538,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -549,6 +552,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ stats_with_context: [] referenced_fields_by_type: {} query_metadata: ~ diff --git a/apollo-router/tests/snapshots/apollo_reports__send_variable_value-2.snap b/apollo-router/tests/snapshots/apollo_reports__send_variable_value-2.snap index e0b887547a..91ebf06aa3 100644 --- a/apollo-router/tests/snapshots/apollo_reports__send_variable_value-2.snap +++ b/apollo-router/tests/snapshots/apollo_reports__send_variable_value-2.snap @@ -185,6 +185,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -428,6 +429,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -535,6 +537,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -548,6 +551,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ stats_with_context: [] referenced_fields_by_type: {} query_metadata: ~ diff --git a/apollo-router/tests/snapshots/apollo_reports__send_variable_value.snap b/apollo-router/tests/snapshots/apollo_reports__send_variable_value.snap index e0b887547a..91ebf06aa3 100644 --- a/apollo-router/tests/snapshots/apollo_reports__send_variable_value.snap +++ b/apollo-router/tests/snapshots/apollo_reports__send_variable_value.snap @@ -185,6 +185,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -428,6 +429,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -535,6 +537,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -548,6 +551,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ stats_with_context: [] referenced_fields_by_type: {} query_metadata: ~ diff --git a/apollo-router/tests/snapshots/apollo_reports__stats.snap b/apollo-router/tests/snapshots/apollo_reports__stats.snap index eb84bc40a2..5a593e3de8 100644 --- a/apollo-router/tests/snapshots/apollo_reports__stats.snap +++ b/apollo-router/tests/snapshots/apollo_reports__stats.snap @@ -19,6 +19,7 @@ traces_per_query: client_version: "" operation_type: query operation_subtype: "" + result: "" query_latency_stats: latency_count: "[latency_count]" request_count: 1 @@ -101,6 +102,9 @@ traces_per_query: estimated_execution_count: 2 requests_with_errors_count: 0 latency_count: "[latency_count]" + local_per_type_stat: {} + limits_stats: ~ + operation_count: 0 referenced_fields_by_type: Product: field_names: diff --git a/apollo-router/tests/snapshots/apollo_reports__stats_mocked.snap b/apollo-router/tests/snapshots/apollo_reports__stats_mocked.snap index 2a872357fa..bf06893e8c 100644 --- a/apollo-router/tests/snapshots/apollo_reports__stats_mocked.snap +++ b/apollo-router/tests/snapshots/apollo_reports__stats_mocked.snap @@ -7,6 +7,7 @@ context: client_version: "" operation_type: query operation_subtype: "" + result: "" query_latency_stats: latency_count: "[latency_count]" request_count: 1 @@ -440,4 +441,6 @@ per_type_stat: - 0 - 0 - 1 - +local_per_type_stat: {} +limits_stats: ~ +operation_count: 0 diff --git a/apollo-router/tests/snapshots/apollo_reports__trace_id-2.snap b/apollo-router/tests/snapshots/apollo_reports__trace_id-2.snap index 140015c24b..3cbf105e67 100644 --- a/apollo-router/tests/snapshots/apollo_reports__trace_id-2.snap +++ b/apollo-router/tests/snapshots/apollo_reports__trace_id-2.snap @@ -183,6 +183,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -426,6 +427,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -533,6 +535,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -546,6 +549,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ stats_with_context: [] referenced_fields_by_type: {} query_metadata: ~ diff --git a/apollo-router/tests/snapshots/apollo_reports__trace_id.snap b/apollo-router/tests/snapshots/apollo_reports__trace_id.snap index 140015c24b..3cbf105e67 100644 --- a/apollo-router/tests/snapshots/apollo_reports__trace_id.snap +++ b/apollo-router/tests/snapshots/apollo_reports__trace_id.snap @@ -183,6 +183,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -426,6 +427,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -533,6 +535,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ sent_time_offset: "[sent_time_offset]" sent_time: seconds: "[seconds]" @@ -546,6 +549,7 @@ traces_per_query: registered_operation: false forbidden_operation: false field_execution_weight: 1 + limits: ~ stats_with_context: [] referenced_fields_by_type: {} query_metadata: ~ diff --git a/dev-docs/telemetry-selectors.md b/dev-docs/telemetry-selectors.md new file mode 100644 index 0000000000..bf909a9995 --- /dev/null +++ b/dev-docs/telemetry-selectors.md @@ -0,0 +1,114 @@ +# Telemetry selectors + +The router has many [selectors](https://www.apollographql.com/docs/router/configuration/telemetry/instrumentation/selectors/) which are really helpful for users wishing to customize their telemetry. With selectors they're able to add custom attributes on [spans](https://www.apollographql.com/docs/router/configuration/telemetry/instrumentation/spans), or create custom [instruments](https://www.apollographql.com/docs/router/configuration/telemetry/instrumentation/instruments) which have custom metrics but also conditionally [log events](https://www.apollographql.com/docs/router/configuration/telemetry/instrumentation/events/). + +Some useful existing selectors are `request_header` to fetch the value of a specific request header, `trace_id` to get the current trace id, `supergraph_query` to get the current query executed at the supergraph level. + +## Goal + +Selectors may be used as a value for an attribute or a metric value and also as a condition. What we would like to achieve in the future is to provide enough flexibility to users so that we can reduce the number of metrics provided by default in the router when adding new features. For example, let's talk about authentication features. We could provide a selector called `authenticated` which would return a boolean specifying that the user executing the query is authenticated (or not). This selector would provide the ability for our users to create their own metrics which specified, for example, how many authenticated metrics are executed. If you combine this with a selector giving the operation cost we could create an histogram indicating the distribution of operation cost for authenticated queries. + +## How to add your own selector ? + +Everything you need to know happens in [this file](https://github.com/apollographql/router/blob/dev/apollo-router/src/plugins/telemetry/config_new/selectors.rs). A selector implements [`Selector` trait](https://github.com/apollographql/router/blob/db741ad683508bb05b1da687e53d6ab00962bb18/apollo-router/src/plugins/telemetry/config_new/mod.rs#L35) which has 2 associated types, one for the `Request` and one for the `Response` type. A `Selector` can happen at different service levels, like `router`|`supergraph`|`subgraph` and so it will let you write the right logic into the methods provided by this trait. You have method `fn on_request(&self, request: &Self::Request) -> Option` to fetch a value from the request itself and `fn on_response(&self, response: &Self::Response) -> Option` to fetch a value from the response. For example if we look at the `request_header` selector it will happen [at the request level](https://github.com/apollographql/router/blob/db741ad683508bb05b1da687e53d6ab00962bb18/apollo-router/src/plugins/telemetry/config_new/selectors.rs#L482). + +You won't have to create new types implementing this `Selector` trait as we already have 3 different kind of `Selector`, [`RouterSelector`](https://github.com/apollographql/router/blob/db741ad683508bb05b1da687e53d6ab00962bb18/apollo-router/src/plugins/telemetry/config_new/selectors.rs#L76), [`SupergraphSelector`](https://github.com/apollographql/router/blob/db741ad683508bb05b1da687e53d6ab00962bb18/apollo-router/src/plugins/telemetry/config_new/selectors.rs#L161) and [`SubgraphSelector`](https://github.com/apollographql/router/blob/db741ad683508bb05b1da687e53d6ab00962bb18/apollo-router/src/plugins/telemetry/config_new/selectors.rs#L276). Both of these types are enum and include different available selectors for each services. + +If you want to define your own selector you just have to add a new variant to these enums and handle the logic properly in the implementation of the `Selector` trait on each enum. Example [here](https://github.com/apollographql/router/blob/db741ad683508bb05b1da687e53d6ab00962bb18/apollo-router/src/plugins/telemetry/config_new/selectors.rs#L473) for `RouterSelector`. If you wanted to add a new selector for authentication and call it `authenticated` you would have to add something like this in the enum: + +```rust +pub(crate) enum SupergraphSelector { + //.... + Authenticated { + /// If the operation is authenticated, set to true to enable it + authenticated: bool, + } + //.... +} +``` + +The implementation would look like this: + +```rust +impl Selector for SupergraphSelector { + type Request = supergraph::Request; + type Response = supergraph::Response; + + fn on_request(&self, request: &supergraph::Request) -> Option { + match self { + // ... + SupergraphSelector::Authenticated { + authenticated + } if *authenticated => { + let is_authenticated = request.context.get::(APOLLO_AUTHENTICATED_USER).ok().flatten(); + match is_authenticated { + Some(is_authenticated) => Some(opentelemetry::Value::Bool(is_authenticated)), + None => None, + } + } + // ... + // For response + _ => None, + } + } +} +``` + +You can test it properly like this: + +```rust +#[test] +fn supergraph_authenticated() { + let selector = SupergraphSelector::Authenticated { + authenticated: true, + }; + let context = crate::context::Context::new(); + let _ = context.insert(APOLLO_AUTHENTICATED_USER, true); + assert_eq!( + selector + .on_request( + &crate::services::SupergraphRequest::fake_builder() + .context(context.clone()) + .build() + .unwrap() + ) + .unwrap(), + true.into() + ); + + assert_eq!( + selector + .on_request( + &crate::services::SupergraphRequest::fake_builder() + .build() + .unwrap() + ), + None + ); +} +``` + +Finally, as an end user you would be able to create your own custom instrument like this: + +```yaml title="router.yaml" +telemetry: + instrumentation: + instruments: + supergraph: + # Custom metric + authenticated_operation: + value: unit + type: counter + unit: operation + description: "Number of authenticated operations" + attributes: + http.response.status_code: true + "my_attribute": + request_header: "x-my-header" + graphql.authenticated: + authenticated: true # Can also be used as a value for an attribute + condition: + eq: + - true + - authenticated: true +``` \ No newline at end of file diff --git a/dockerfiles/tracing/docker-compose.datadog.yml b/dockerfiles/tracing/docker-compose.datadog.yml index 79a6692a8a..f90fff4010 100644 --- a/dockerfiles/tracing/docker-compose.datadog.yml +++ b/dockerfiles/tracing/docker-compose.datadog.yml @@ -3,7 +3,7 @@ services: apollo-router: container_name: apollo-router - image: ghcr.io/apollographql/router:v1.46.0 + image: ghcr.io/apollographql/router:v1.47.0 volumes: - ./supergraph.graphql:/etc/config/supergraph.graphql - ./router/datadog.router.yaml:/etc/config/configuration.yaml diff --git a/dockerfiles/tracing/docker-compose.jaeger.yml b/dockerfiles/tracing/docker-compose.jaeger.yml index 8d047807cf..55605308eb 100644 --- a/dockerfiles/tracing/docker-compose.jaeger.yml +++ b/dockerfiles/tracing/docker-compose.jaeger.yml @@ -4,7 +4,7 @@ services: apollo-router: container_name: apollo-router #build: ./router - image: ghcr.io/apollographql/router:v1.46.0 + image: ghcr.io/apollographql/router:v1.47.0 volumes: - ./supergraph.graphql:/etc/config/supergraph.graphql - ./router/jaeger.router.yaml:/etc/config/configuration.yaml diff --git a/dockerfiles/tracing/docker-compose.zipkin.yml b/dockerfiles/tracing/docker-compose.zipkin.yml index 0e98fb085e..1689a4e9d4 100644 --- a/dockerfiles/tracing/docker-compose.zipkin.yml +++ b/dockerfiles/tracing/docker-compose.zipkin.yml @@ -4,7 +4,7 @@ services: apollo-router: container_name: apollo-router build: ./router - image: ghcr.io/apollographql/router:v1.46.0 + image: ghcr.io/apollographql/router:v1.47.0 volumes: - ./supergraph.graphql:/etc/config/supergraph.graphql - ./router/zipkin.router.yaml:/etc/config/configuration.yaml diff --git a/docs/source/configuration/telemetry/apollo-telemetry.mdx b/docs/source/configuration/telemetry/apollo-telemetry.mdx index 7009ec3d4d..50c6e63935 100644 --- a/docs/source/configuration/telemetry/apollo-telemetry.mdx +++ b/docs/source/configuration/telemetry/apollo-telemetry.mdx @@ -34,16 +34,17 @@ Your subgraph libraries must support federated tracing (also known as FTV1 traci - Consult your library's documentation to learn how to enable federated tracing. - If you use Apollo Server with `@apollo/subgraph`, federated tracing support is enabled automatically. -### Trace sampling rate +### Subgraph trace sampling -By default, the Apollo Router requests subgraph trace data for 1% of operations. In most cases, this provides a sufficient sample size while minimizing latency for most operations (traces can affect latency because they increase the size of subgraph response payloads). +By default, the Apollo Router requests subgraph trace data from operations with a 1% sampling probability per operation. In most cases, this provides a sufficient sample size while minimizing latency for most operations (traces can affect latency because they increase the size of subgraph response payloads). -You can customize your router's trace sampling rate by setting the following options in your [YAML config file](./overview/#yaml-config-file): +You can customize your router's trace sampling probability by setting the following options in your [YAML config file](./overview/#yaml-config-file): ```yaml title="router.yaml" telemetry: apollo: - # In this example, the router will request traces for 50% of requests. + # In this example, the trace sampler is configured + # with a 50% probability of sampling a request. # This value can't exceed the value of tracing.common.sampler. field_level_instrumentation_sampler: 0.5 diff --git a/docs/source/configuration/telemetry/exporters/logging/stdout.mdx b/docs/source/configuration/telemetry/exporters/logging/stdout.mdx index 7a4d6b808a..a96bf36bdd 100644 --- a/docs/source/configuration/telemetry/exporters/logging/stdout.mdx +++ b/docs/source/configuration/telemetry/exporters/logging/stdout.mdx @@ -154,8 +154,7 @@ Example `text` output: | Option | Values | Default | Description | |-----------------------------|--------------------------------|-----------|-------------------------------------------------------------------------| -| `flavor` | `default`\|`compact`\|`pretty` | `default` | The overall output formatting. | -| `ansi_escape_codes` | `true`\|`false` | `false` | Use ansi terminal escape codes. | +| `ansi_escape_codes` | `true`\|`false` | `true` | Use ansi terminal escape codes. | | `display_filename` | `true`\|`false` | `false` | The filename where the log event was raised. | | `display_level` | `true`\|`false` | `true` | The level of the log event, e.g. INFO, WARN, ERROR, TRACE. | | `display_line_number` | `true`\|`false` | `false` | The line number where the event was raised. | @@ -167,6 +166,8 @@ Example `text` output: | `display_service_namespace` | `true`\|`false` | `false` | The service namespace as configured in metrics common. | | `display_trace_id` | `true`\|`false` | `false` | The trace id of the span in which the event was raised. | | `display_span_id` | `true`\|`false` | `false` | The span ID of the span in which the event was raised. | +| `display_span_list` | `true`\|`false` | `true` | A list of all spans to root in which the event was raised and all of their attributes. | +| `display_current_span` | `true`\|`false` | `true` | The span in which the event was raised and all of its' attributes. | ### `json` @@ -362,15 +363,15 @@ telemetry: | Option | Values | Default | Event Field | Description | |-----------------------|-------------------|---------|:--------------|----------------------------------------------------------------------------------------| -| `current_span` | `true`\|`false` | `false` | `span` | The span in which the event was raised and all of its' attributes. | +| `display_current_span`| `true`\|`false` | `false` | `span` | The span in which the event was raised and all of its' attributes. | | `display_filename` | `true`\|`false` | `false` | `filename` | The filename where the log event was raised. | | `display_level` | `true`\|`false` | `true` | `level` | The level of the log event, e.g. INFO, WARN, ERROR, TRACE. | | `display_line_number` | `true`\|`false` | `false` | `line_number` | The line number where the event was raised. | -| `display_target` | `true`\|`false` | `false` | `target` | The module name where the event was raised. | +| `display_target` | `true`\|`false` | `true` | `target` | The module name where the event was raised. | | `display_thread_id` | `true`\|`false` | `false` | `thread_id` | The id of the thread where the event was raised. | | `display_thread_name` | `true`\|`false` | `false` | `thread_name` | The name of the thread where the event was raised. | | `display_timestamp` | `true`\|`false` | `true` | `timestamp` | The timestamp of when the event was raised. | -| `display_span_list` | `true`\|`false` | `false` | `spans` | A list of all spans to root in which the event was raised and all of their attributes. | +| `display_span_list` | `true`\|`false` | `true` | `spans` | A list of all spans to root in which the event was raised and all of their attributes. | | `display_resource` | `true`\|`false` | `true` | `resource` | The resource as configured in tracing common. | | `display_trace_id` | `true`\|`false` | `true` | `trace_id` | The trace id of the span in which the event was raised. | | `display_span_id` | `true`\|`false` | `true` | `span_id` | The span id of the span in which the event was raised. | diff --git a/docs/source/configuration/telemetry/exporters/metrics/overview.mdx b/docs/source/configuration/telemetry/exporters/metrics/overview.mdx index fad0471ff1..12f105df30 100644 --- a/docs/source/configuration/telemetry/exporters/metrics/overview.mdx +++ b/docs/source/configuration/telemetry/exporters/metrics/overview.mdx @@ -75,7 +75,7 @@ If the service name isn't explicitly set, it defaults to `unknown_service:router ### `resource` -A resource attribute is a set of key-value pairs that provide additional information to an exporter. Application performance monitors (APM) may interpret and display resource information. +A resource attribute is a set of key-value pairs that provide additional information to an exporter. It's an attribute of an [OpenTelemetry resource](https://opentelemetry.io/docs/specs/otel/resource/sdk/). Application performance monitors (APM) can interpret and display resource information. In [`router.yaml`](../../../overview/#yaml-config-file), resource attributes are set in `telemetry.metrics.common.resource`. For example: @@ -123,6 +123,12 @@ You can add custom attributes (OpenTelemetry) and labels (Prometheus) to the `ap * a value from a context * a value from the request or response body ([JSON path](https://goessner.net/articles/JsonPath/)) + + +Use [resource attributes](#resource) instead to provide information about telemetry resources, including hosts and environments. + + + An example of configuring these attributes is shown below: ```yaml title="router.yaml" diff --git a/docs/source/configuration/telemetry/instrumentation/conditions.mdx b/docs/source/configuration/telemetry/instrumentation/conditions.mdx index 487b1aac0f..9b8c569dbf 100644 --- a/docs/source/configuration/telemetry/instrumentation/conditions.mdx +++ b/docs/source/configuration/telemetry/instrumentation/conditions.mdx @@ -68,6 +68,34 @@ eq: Values may be of types `string`, `number` or `boolean`. +#### `gt` + +The `gt` condition checks that one value is greater than another. + +For example, the following condition checks the response status code is greater than 299: + +```yaml +gt: + - response_status: code + - 299 +``` + +Values may be of types `string`, `number` or `boolean`. + +#### `lt` + +The `lt` condition checks that one value is less than another. + +For example, the following condition checks the response status code is less than 500: + +```yaml +lt: + - response_status: code + - 500 +``` + +Values may be of types `string`, `number` or `boolean`. + #### `not` The `not` condition is a negation of the nested condition. @@ -120,6 +148,8 @@ The available basic conditions: | Condition | Description | |----------|----------------------------------------------------------| | `eq` | An equality test between selectors or values | +| `gt` | An inequality test between selectors or values | +| `lt` | An inequality test between selectors or values | | `exist` | A check to see if the selectors value exists | | `not` | A negated equality test between selectors or values | | `all` | A list of conditions that must all be true | diff --git a/docs/source/configuration/telemetry/instrumentation/events.mdx b/docs/source/configuration/telemetry/instrumentation/events.mdx index d2bd1844a2..6fbf67a720 100644 --- a/docs/source/configuration/telemetry/instrumentation/events.mdx +++ b/docs/source/configuration/telemetry/instrumentation/events.mdx @@ -98,7 +98,7 @@ telemetry: ### `on` -Each custom event must indicate when it should be triggered. This can be `request`, `response` or `error`. +Each custom event must indicate when it should be triggered. This can be `request`, `response`, `event_response` or `error`. ```yaml title="future.router.yaml" telemetry: @@ -106,13 +106,15 @@ telemetry: events: router: acme.event: - on: request # request, response, error + on: request # request, response, event_response, error # ... ``` +> `event_response` is useful when you want to directly access to the json response body. It also works for subscription events and `@defer` chunks. + ### `level` -Custom events have a level, `trace`, `debug`, `info`, `warn` or `error`. The level determines the severity of the event. +Custom events have a level, `trace`, `debug`, `info`, `warn`, `error` or `off` (if you want to disable this event). The level determines the severity of the event. To set the level: ```yaml title="future.router.yaml" @@ -120,7 +122,7 @@ telemetry: events: router: acme.event: - level: info # trace, debug, info, warn, error + level: info # trace, debug, info, warn, error, off # ... ``` @@ -202,8 +204,8 @@ telemetry: | Option | Values | Default | Description | |--------------------|------------------------------------------------------------------------------|---------|-------------------------------------------------------------| | `` | | | The name of the custom attribute. | -| `attributes` | [standard attributes](./standard-attributes) or [selectors](./selectors) | | The attributes of the custom log event. | -| `condition` | [conditions](./conditions) | | The condition that must be met for the event to be emitted. | +| `attributes` | [standard attributes](./standard-attributes) or [selectors](./selectors) | | The attributes of the custom log event. | +| `condition` | [conditions](./conditions) | | The condition that must be met for the event to be emitted. | | `error` | `trace`\|`info`\|`warn`\|`error`\| `off` | `off` | The level of the error log event. | | `level` | `trace`\|`info`\|`warn`\|`error`\| `off` | `off` | The level of the custom log event. | | `message` | | | The message of the custom log event. | diff --git a/docs/source/configuration/telemetry/instrumentation/instruments.mdx b/docs/source/configuration/telemetry/instrumentation/instruments.mdx index b8837a9c1b..1861519cdb 100644 --- a/docs/source/configuration/telemetry/instrumentation/instruments.mdx +++ b/docs/source/configuration/telemetry/instrumentation/instruments.mdx @@ -199,6 +199,9 @@ The `value` of an instrument is the value which will be drawn from. This can be * `duration` - the duration of the pipeline service. * `unit` - the number of times the pipeline service has been executed. * `custom` - a custom value extracted from the pipeline service. See [selectors](./selectors) for more information. +* `event_duration` - the duration of an event in the pipeline service. +* `event_unit` - the number of times an event in the pipeline service has been executed. +* `event_custom` - a custom value extracted from the event in the pipeline service. See [selectors](./selectors) for more information. ```yaml title="future.router.yaml" telemetry: @@ -210,6 +213,8 @@ telemetry: value: duration ``` +> `event_*` are mandantory when you want to use a [selector](./selectors) on the supergraph response body (`response_data` and `response_errors`). + Values of custom metrics can be extracted from the pipeline using custom attributes. For example, to sum the contents of a request header, create a counter with value set as the request header: ```yaml title="future.router.yaml" @@ -336,6 +341,18 @@ telemetry: "my_attribute": # ... response_header: "x-my-header" + 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 ``` ### Instrument configuration reference diff --git a/docs/source/configuration/telemetry/instrumentation/selectors.mdx b/docs/source/configuration/telemetry/instrumentation/selectors.mdx index f4c0398a56..5a6db45666 100644 --- a/docs/source/configuration/telemetry/instrumentation/selectors.mdx +++ b/docs/source/configuration/telemetry/instrumentation/selectors.mdx @@ -41,6 +41,7 @@ The router service is the initial entrypoint for all requests. It is HTTP centri | `env` | Yes | | The name of an environment variable | | `on_graphql_error` | No | `true`|`false` | Boolean set to true if the response payload contains a graphql error | | `static` | No | | A static string value | +| `error` | No | `reason` | a string value containing error reason when it's a critical error | #### Supergraph @@ -54,11 +55,14 @@ The supergraph service is executed after query parsing but before query executio | `query_variable` | Yes | | The name of a graphql query variable | | `request_header` | Yes | | The name of a request header | | `response_header` | Yes | | The name of a response header | +| `response_data` | Yes | | Json Path into the supergraph response body data (it might impact performances) | +| `response_errors` | Yes | | Json Path into the supergraph response body errors (it might impact performances) | | `request_context` | Yes | | The name of a request context key | | `response_context` | Yes | | The name of a response context key | | `baggage` | Yes | | The name of a baggage item | | `env` | Yes | | The name of an environment variable | | `static` | No | | A static string value | +| `error` | No | `reason` | a string value containing error reason when it's a critical error | #### Subgraph @@ -85,3 +89,4 @@ The subgraph service executes multiple times during query execution, with each e | `baggage` | Yes | | The name of a baggage item | | `env` | Yes | | The name of an environment variable | | `static` | No | | A static string value | +| `error` | No | `reason` | a string value containing error reason when it's a critical error | diff --git a/docs/source/configuration/telemetry/instrumentation/standard-attributes.mdx b/docs/source/configuration/telemetry/instrumentation/standard-attributes.mdx index b5ca205c2c..2961909ba0 100644 --- a/docs/source/configuration/telemetry/instrumentation/standard-attributes.mdx +++ b/docs/source/configuration/telemetry/instrumentation/standard-attributes.mdx @@ -37,7 +37,6 @@ Standard attributes of the `router` service: | `error.type` | | Describes a class of error the operation ended with | | `http.request.body.size` | | The size of the request payload body in bytes | | `http.request.method` | | HTTP request method | -| `http.request.method.original` | | Original HTTP method sent by the client in the request line | | `http.response.body.size` | | The size of the response payload body in bytes | | `http.response.status_code` | | HTTP response status code | | `network.protocol.name` | | OSI application layer or non-OSI equivalent | @@ -45,8 +44,6 @@ Standard attributes of the `router` service: | `network.transport` | | OSI transport layer | | `network.type` | | OSI network layer or non-OSI equivalent | | `user_agent.original` | | Value of the HTTP User-Agent header sent by the client | -| `client.address` | | Client address - domain name if available without reverse DNS lookup, otherwise IP address or Unix domain socket name | -| `client.port` | | The port of the original client behind all proxies, if known (e.g. from Forwarded or a similar header). Otherwise, the immediate client peer port | | `http.route` | | The matched route (path template in the format used by the respective server framework) | | `network.local.address` | | Local socket address. Useful in case of a multi-IP host | | `network.local.port` | | Local socket port. Useful in case of a multi-port host | diff --git a/docs/source/customizations/native.mdx b/docs/source/customizations/native.mdx index dabb6edf9f..616e051cf4 100644 --- a/docs/source/customizations/native.mdx +++ b/docs/source/customizations/native.mdx @@ -206,6 +206,16 @@ Use `upsert` if you might need to resolve multiple simultaneous writes to a sing Note: `upsert` requires v to implement `Default`. +#### `enter_active_request` + +```rust +let _guard = context.enter_active_request(); +http_client.request().await; +drop(_guard); +``` + +The Router measures how much time it spends working on a request, by subtracting the time spent waiting on network calls, like subgraphs or coprocessors. The result is reported in the `apollo_router_processing_time` metric. If the native plugin is performing network calls, then they should be taken into account in this metric. It is done by calling the `enter_active_request` method, which returns a guard value. Until that value is dropped, the Router will consider that a network request is happening. + ### 6. Register your plugin To enable the Apollo Router to discover your plugin, you need to **register** the plugin. diff --git a/docs/source/customizations/rhai-api.mdx b/docs/source/customizations/rhai-api.mdx index 00dd9adbf5..b0eb1be3cd 100644 --- a/docs/source/customizations/rhai-api.mdx +++ b/docs/source/customizations/rhai-api.mdx @@ -540,6 +540,7 @@ The `response` object includes the following fields: ``` response.context response.id +response.status_code response.headers response.body.label response.body.data @@ -618,3 +619,29 @@ let error_to_add = #{ response.body.errors += error_to_add; print(`${response.body.errors}`); // logs the response errors ``` + +### `response.status_code.to_string()` + +Convert response status code to a string. + +```rhai +if response.status_code.to_string() == "200" { + print(`ok`); +} +``` + +Also useful if you want to convert response status code to a number + +```rhai +if parse_int(response.status_code.to_string()) == 200 { + print(`ok`); +} +``` + +You can also create your own status code from an integer: + +```rhai +if response.status_code == status_code_from_int(200) { + print(`ok`); +} +``` \ No newline at end of file diff --git a/docs/source/executing-operations/file-uploads.mdx b/docs/source/executing-operations/file-uploads.mdx new file mode 100644 index 0000000000..4e2ca4b73a --- /dev/null +++ b/docs/source/executing-operations/file-uploads.mdx @@ -0,0 +1,420 @@ +--- +title: File uploads +subtitle: Receive files uploaded by clients with the Apollo Router +description: Configure the Apollo Router to receive file uploads using the GraphQL multipart request spec. +minVersion: 1.41.1 +noIndex: true +--- + + + +This feature is in invite-only [preview](/resources/product-launch-stages/#product-launch-stages) for organization with an Enterprise plan. Get in touch with your Apollo contact to request access. + + + +Learn how to configure the Apollo Router to receive file uploads in client requests using the [GraphQL multipart request specification](https://github.com/jaydenseric/graphql-multipart-request-spec). + +## About file uploads using multipart requests + +A [multipart HTTP request](https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html) lets you efficiently send multiple files of various data formats—such as text, binary, and JSON objects—in a single HTTP request. The [GraphQL multipart request spec](https://github.com/jaydenseric/graphql-multipart-request-spec) uses multipart requests to upload files using arguments in a GraphQL mutation. + +### Example usage + +Imagine you're building a platform where users can create posts with a title and an image file. +Your subgraph schema may include something like this: + +```graphql showLineNumbers=false disableCopy +type Post { + id: ID! + title: String! + image: Upload! +} + +type Mutation { + createPost(title: String!, image: Upload!): Post! +} +``` + + + +Some GraphQL server implementations provide built-in support for handling file uploads, including an `Upload` scalar type. +For others, including the latest version of [Apollo Server](/apollo-server/), you must use external packages, such as [`graphql-upload`](https://www.npmjs.com/package/graphql-upload). +Refer to your subgraph library or package documentation for further information, including writing resolvers for uploaded files. + + + +When a client calls the `createPost` mutation, it can use variables to include the actual image file to upload: + +```graphql showLineNumbers=false disableCopy +{ + query: ` + mutation CreatePost($title: String!, $image: Upload!) { + createPost(title: $title, image: $image) { + id + title + image + } + } + `, + variables: { + title: "My first post", + image: File // image.png + } +} +``` + +A request using the GraphQL multipart request spec would include the following as separate parts in a multipart request: + +- the above operation definition +- the image file to upload +- a map between variables and files to upload + +The exact requirements are documented in [Client usage requirements](#client-usage-requirements). + +## File upload configuration and usage + +To enable file uploads from clients, you must both [configure support in the Apollo Router](#configure-file-upload-support-in-the-router) and [ensure client usage conforms to requirements](#client-usage-requirements). + +### Configure file upload support in the router + +By default, receiving client file uploads isn't enabled in the Apollo Router. +To enable file upload support, set the following fields in your `router.yaml` configuration file: + +```yaml title="router.yaml" +preview_file_uploads: + enabled: true + protocols: + multipart: + enabled: true + mode: stream + limits: + max_file_size: 1mb + max_files: 10 +``` + +#### Mode + +The only supported `mode` is `stream`. +That means the router doesn't retain uploaded files in memory during a request. +Streaming file uploads can be more memory-efficient, especially for large files, since it avoids loading the entire file into memory. + +To ensure your operation is streamable, avoid nesting file uploads. +For example, the following `nestedUpload` operation attempting to upload `$file3` would not be streamable: + +```graphql title="❌" disableCopy=true showLineNumbers=false +mutation uploadFiles($file1: Upload, $file3: Upload, $file3: Upload ) { + upload(data: $file1) { + __typename + } + upload(data: $file2) { + __typename + } + + nestedUpload { + upload(data: $file3) { + __typename + } + } +} +``` + +If a request cannot be fulfilled in a streaming fashion, the router returns the [`UPLOADS_OPERATION_CANNOT_STREAM`](#uploads_operation_cannot_stream) error. + +#### Limits + +The router includes default limits for file uploads to prevent denial-of-service attacks. +You can configure both the maximum file size and number of files to accept. +If a request exceeds a limit, the router rejects the request. + +#### Configuration reference + +The following are attributes of the root [`preview_file_uploads`](#configure-file-upload-support-in-the-router) configuration. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Attribute/
Description
Default ValueValid Values
+ +##### `enabled` + +Flag to enable reception of client file uploads + + + + +`false` + +boolean
+ +##### `protocols.multipart.enabled` + +Flag to enable reception of multipart file uploads + + + +`false` + +boolean
+ +##### `protocols.multipart.mode` + +Supported file upload mode + + + +`stream` + + + +`stream` + +
+ +##### `protocols.multipart.limits.max_file_size` + +The maximum file size to accept. +If this limit is exceeded, the router rejects the entire request. + + + +`512kb` + + + +values in a [human-readable format](https://crates.io/crates/bytesize), for example, `5kb` and `99mb` + +
+ +##### `protocols.multipart.limits.max_files` + +The maximum number of files to accept. +If this limit is exceeded, the router rejects the entire request. + + + +`5` + +integer
+ + +### Client usage requirements + +When calling a mutation with file uploads, the client must send the following HTTP parts in the following order: + +1. The raw GraphQL operation +1. A map of file(s) to variable name(s) +1. The files to be uploaded, one HTTP request part per file + +#### Example request payload + +The following is an example of a multipart HTTP request payload that builds off the [example scenario](#example-usage): + +```http disableCopy showLineNumbers=false title="Request payload" +--------------------------gc0p4Jq0M2Yt08jU534c0p +Content-Disposition: form-data; name="operations" + +{ "query": "mutation CreatePost($title: String!, $image: Upload!) { createPost(title: $title, image: $image) { id } }", "variables": { "title": "My first post", "image": null } } +--------------------------gc0p4Jq0M2Yt08jU534c0p +Content-Disposition: form-data; name="map" + +{ "0": ["variables.image"] } +--------------------------gc0p4Jq0M2Yt08jU534c0p +Content-Disposition: form-data; name="0"; filename="image.png" +Content-Type: image/png + +[Binary image content here] +--------------------------gc0p4Jq0M2Yt08jU534c0p-- +``` + +See below for an explanation of each part of the request payload: + +- **`Content-Disposition: form-data; name="operations"`** + - The first part of the request must include the operation definition. This example specifies a mutation named `CreatePost` that accepts variables for a `title` and `image`. + - The `variables` object includes the title for the post and sets the `image` variable to `null` as the [multipart request spec](https://github.com/jaydenseric/graphql-multipart-request-spec) requires for any variables that represent files to be uploaded. +- **`Content-Disposition: form-data; name="map"`** + - The second part of the request must include the mapping between the files to upload and the variables in the GraphQL operation. + - In this case, it maps the file in the request part with `name="0"` to `variables.image`. The map can use any key names you like—for example, `file1` instead of `0`—as long as the keys match the `name`s of the following request parts. +- **`Content-Disposition: form-data; name="0"; filename="image.png"`** + - The following part(s) contain the actual file(s) to be uploaded, with one file per part. The order of the files must match the order they're declared in the map in the second part of the request. + - In this case, there is only one file to upload, which has the name `image.png` and the appropriate content type (`image/png`) + - These parts also include actual file content—in this case, an image binary. + +Each part of the request payload is separated by a boundary string (`gc0p4Jq0M2Yt08jU534c0p`) per the [multipart request format](https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html). + +Refer to the docs for your client library for further instructions. + +- [Apollo Client (web)](/react/data/file-uploads/) +- [Apollo iOS](/ios/file-uploads/) +- [Apollo Kotlin](/kotlin/advanced/upload/) + +Custom clients can be implemented following the [spec documentation](https://github.com/jaydenseric/graphql-multipart-request-spec). + +## Security + +Without additional security, HTTP multipart requests can be exploited as part of [cross-site request forgery](https://owasp.org/www-community/attacks/csrf) (CSRF) attacks. + +The Apollo Router already has a mechanism to prevent these types of attacks, which is enabled by default. You should verify that your router hasn't disabled this mechanism before using file uploads. See [Cross-Site Request Forgery Prevention](../configuration/csrf) for details. + +## Metrics for file uploads + +Metrics in the Apollo Router for file uploads: + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameDescription
+ +##### `apollo.router.operations.file_uploads` + + + +Counter for the number of file uploads + +
+ +##### `apollo.router.operations.file_uploads.file_size` + + + +Histogram for the size of uploaded files + +
+ +##### `apollo.router.operations.file_uploads.files` + + + +Histogram for the number of uploaded files + +
+ +## Error codes for file uploads + +A file upload request may receive the following error responses: + + + + + + + + + + + + + + + + + + + + + + +
Error CodeDescription
+ +##### `UPLOADS_LIMITS_MAX_FILES_EXCEEDED` + +The number of files in the request exceeded the configured limit
+ +##### `UPLOADS_LIMITS_MAX_FILE_SIZE_EXCEEDED` + +A file exceeded the maximum configured file size
+ +##### `UPLOADS_FILE_MISSING` + +The operation specified a file that was missing from the request
+ +##### `UPLOADS_OPERATION_CANNOT_STREAM` + +The request was invalid as it couldn't be streamed to the client
+ + +## Known limitations + +While in private preview, Apollo recommends using file uploads only in development and testing environments, not in production. + +### Unsupported query modes + +The router rejects operations that use file upload variables on or inside fields using [`@defer`](/graphos/operations/defer/). + + + +```graphql title="❌ Unsupported usage" disableCopy=true showLineNumbers=false +query ($file: Upload) { + someField { + ... @defer { + anotherField(file: $file) + } + } +} +``` + +```graphql title="✅ Supported usage" disableCopy=true showLineNumbers=false +query ($file: Upload) { + someField(file: $file) { + ... @defer { + anotherField + } + } +} +``` + + \ No newline at end of file diff --git a/examples/graphql/supergraph-fed2.graphql b/examples/graphql/supergraph-fed2.graphql new file mode 100644 index 0000000000..504fbbaafb --- /dev/null +++ b/examples/graphql/supergraph-fed2.graphql @@ -0,0 +1,98 @@ +schema + @link(url: "https://specs.apollo.dev/link/v1.0") + @link(url: "https://specs.apollo.dev/join/v0.3", for: EXECUTION) +{ + query: Query + mutation: Mutation +} + +directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE + +directive @join__field(graph: join__Graph, requires: join__FieldSet, provides: join__FieldSet, type: String, external: Boolean, override: String, usedOverridden: Boolean) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION + +directive @join__graph(name: String!, url: String!) on ENUM_VALUE + +directive @join__implements(graph: join__Graph!, interface: String!) repeatable on OBJECT | INTERFACE + +directive @join__type(graph: join__Graph!, key: join__FieldSet, extension: Boolean! = false, resolvable: Boolean! = true, isInterfaceObject: Boolean! = false) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR + +directive @join__unionMember(graph: join__Graph!, member: String!) repeatable on UNION + +directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA + +scalar join__FieldSet + +enum join__Graph { + ACCOUNTS @join__graph(name: "accounts", url: "https://accounts.demo.starstuff.dev/") + INVENTORY @join__graph(name: "inventory", url: "https://inventory.demo.starstuff.dev/") + PRODUCTS @join__graph(name: "products", url: "https://products.demo.starstuff.dev/") + REVIEWS @join__graph(name: "reviews", url: "https://reviews.demo.starstuff.dev/") +} + +scalar link__Import + +enum link__Purpose { + """ + `SECURITY` features provide metadata necessary to securely resolve fields. + """ + SECURITY + + """ + `EXECUTION` features provide metadata necessary for operation execution. + """ + EXECUTION +} + +type Mutation + @join__type(graph: PRODUCTS) + @join__type(graph: REVIEWS) +{ + createProduct(upc: ID!, name: String): Product @join__field(graph: PRODUCTS) + createReview(upc: ID!, id: ID!, body: String): Review @join__field(graph: REVIEWS) +} + +type Product + @join__type(graph: ACCOUNTS, key: "upc", extension: true) + @join__type(graph: INVENTORY, key: "upc") + @join__type(graph: PRODUCTS, key: "upc") + @join__type(graph: REVIEWS, key: "upc") +{ + upc: String! + weight: Int @join__field(graph: INVENTORY, external: true) @join__field(graph: PRODUCTS) + price: Int @join__field(graph: INVENTORY, external: true) @join__field(graph: PRODUCTS) + inStock: Boolean @join__field(graph: INVENTORY) + shippingEstimate: Int @join__field(graph: INVENTORY, requires: "price weight") + name: String @join__field(graph: PRODUCTS) + reviews: [Review] @join__field(graph: REVIEWS) + reviewsForAuthor(authorID: ID!): [Review] @join__field(graph: REVIEWS) +} + +type Query + @join__type(graph: ACCOUNTS) + @join__type(graph: INVENTORY) + @join__type(graph: PRODUCTS) + @join__type(graph: REVIEWS) +{ + me: User @join__field(graph: ACCOUNTS) + recommendedProducts: [Product] @join__field(graph: ACCOUNTS) + topProducts(first: Int = 5): [Product] @join__field(graph: PRODUCTS) +} + +type Review + @join__type(graph: REVIEWS, key: "id") +{ + id: ID! + body: String + author: User @join__field(graph: REVIEWS, provides: "username") + product: Product +} + +type User + @join__type(graph: ACCOUNTS, key: "id") + @join__type(graph: REVIEWS, key: "id") +{ + id: ID! + name: String @join__field(graph: ACCOUNTS) + username: String @join__field(graph: ACCOUNTS) @join__field(graph: REVIEWS, external: true) + reviews: [Review] @join__field(graph: REVIEWS) +} diff --git a/helm/chart/router/Chart.yaml b/helm/chart/router/Chart.yaml index 756c9b6baf..06b900da23 100644 --- a/helm/chart/router/Chart.yaml +++ b/helm/chart/router/Chart.yaml @@ -20,10 +20,10 @@ type: application # so it matches the shape of our release process and release automation. # By proxy of that decision, this version uses SemVer 2.0.0, though the prefix # of "v" is not included. -version: 1.46.0 +version: 1.47.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "v1.46.0" +appVersion: "v1.47.0" diff --git a/helm/chart/router/README.md b/helm/chart/router/README.md index 77956fb709..eed82179b9 100644 --- a/helm/chart/router/README.md +++ b/helm/chart/router/README.md @@ -2,7 +2,7 @@ [router](https://github.com/apollographql/router) Rust Graph Routing runtime for Apollo Federation -![Version: 1.46.0](https://img.shields.io/badge/Version-1.46.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v1.46.0](https://img.shields.io/badge/AppVersion-v1.46.0-informational?style=flat-square) +![Version: 1.47.0](https://img.shields.io/badge/Version-1.47.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v1.47.0](https://img.shields.io/badge/AppVersion-v1.47.0-informational?style=flat-square) ## Prerequisites @@ -11,7 +11,7 @@ ## Get Repo Info ```console -helm pull oci://ghcr.io/apollographql/helm-charts/router --version 1.46.0 +helm pull oci://ghcr.io/apollographql/helm-charts/router --version 1.47.0 ``` ## Install Chart @@ -19,7 +19,7 @@ helm pull oci://ghcr.io/apollographql/helm-charts/router --version 1.46.0 **Important:** only helm3 is supported ```console -helm upgrade --install [RELEASE_NAME] oci://ghcr.io/apollographql/helm-charts/router --version 1.46.0 --values my-values.yaml +helm upgrade --install [RELEASE_NAME] oci://ghcr.io/apollographql/helm-charts/router --version 1.47.0 --values my-values.yaml ``` _See [configuration](#configuration) below._ diff --git a/licenses.html b/licenses.html index 79f5e66beb..9461358688 100644 --- a/licenses.html +++ b/licenses.html @@ -48,8 +48,8 @@

Overview of licenses:

  • MIT License (152)
  • BSD 3-Clause "New" or "Revised" License (12)
  • ISC License (11)
  • +
  • Elastic License 2.0 (6)
  • BSD 2-Clause "Simplified" License (3)
  • -
  • Elastic License 2.0 (3)
  • Mozilla Public License 2.0 (3)
  • Creative Commons Zero v1.0 Universal (2)
  • OpenSSL License (2)
  • @@ -8242,7 +8242,6 @@

    Used by:

  • either
  • envmnt
  • equivalent
  • -
  • errno
  • event-listener
  • fastrand
  • fastrand
  • @@ -8294,6 +8293,7 @@

    Used by:

  • mockall_derive
  • multimap
  • multimap
  • +
  • multimap
  • num
  • num-bigint
  • num-bigint-dig
  • @@ -13163,6 +13163,9 @@

    Elastic License 2.0

    Used by:

    Copyright 2021 Apollo Graph, Inc.
     
    @@ -13265,11 +13268,15 @@ 

    Used by:

    Elastic License 2.0

    Used by:

    -
    Elastic License 2.0
    +                
    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.
     
    -URL: https://www.elastic.co/licensing/elastic-license
    +--------------------------------------------------------------------------------
    +
    +Elastic License 2.0
     
     ## Acceptance
     
    @@ -13360,7 +13367,8 @@ 

    Used by:

    **use** means anything you do with the software requiring one of your licenses. **trademark** means trademarks, service marks, and similar rights. -
    + +--------------------------------------------------------------------------------
  • ISC License

    diff --git a/scripts/install.sh b/scripts/install.sh index dad2182d10..3b509ebc6b 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -11,7 +11,7 @@ BINARY_DOWNLOAD_PREFIX="https://github.com/apollographql/router/releases/downloa # Router version defined in apollo-router's Cargo.toml # Note: Change this line manually during the release steps. -PACKAGE_VERSION="v1.46.0" +PACKAGE_VERSION="v1.47.0" download_binary() { downloader --check diff --git a/xtask/src/commands/test.rs b/xtask/src/commands/test.rs index efb84af491..dab1db991a 100644 --- a/xtask/src/commands/test.rs +++ b/xtask/src/commands/test.rs @@ -21,6 +21,10 @@ pub struct Test { /// Pass --workspace to cargo test #[clap(long)] workspace: bool, + + /// Pass --features to cargo test + #[clap(long)] + features: Option, } impl Test { @@ -47,6 +51,11 @@ impl Test { args.push("--workspace".to_string()); } + if let Some(features) = &self.features { + args.push("--features".to_string()); + args.push(features.to_owned()); + } + cargo!(args); return Ok(()); } else {