├── .gitignore ├── .gitleaks.toml ├── .github ├── CODEOWNERS └── renovate.json5 ├── Cargo.toml ├── CHANGELOG.md ├── apollo-composition ├── README.md ├── Cargo.toml ├── RELEASE_CHECKLIST.md ├── CHANGELOG.md └── src │ └── lib.rs ├── apollo-federation-types ├── README.md ├── src │ ├── lib.rs │ ├── config │ │ ├── mod.rs │ │ ├── config_error.rs │ │ ├── subgraph.rs │ │ ├── version.rs │ │ └── supergraph.rs │ ├── build_plugin │ │ ├── mod.rs │ │ ├── build_message.rs │ │ └── plugin_result.rs │ ├── rover │ │ ├── mod.rs │ │ ├── hint.rs │ │ ├── output.rs │ │ └── error.rs │ ├── javascript │ │ └── mod.rs │ └── composition │ │ └── mod.rs ├── RELEASE_CHECKLIST.md ├── Cargo.toml └── CHANGELOG.md ├── macos-entitlements.plist ├── README.md ├── LICENSE ├── .circleci └── config.yml └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /artifacts 3 | **/.DS_Store 4 | .idea/ 5 | .env 6 | -------------------------------------------------------------------------------- /.gitleaks.toml: -------------------------------------------------------------------------------- 1 | [[ rules ]] 2 | id = "generic-api-key" 3 | [ rules.allowlist ] 4 | paths = [ 5 | '''Cargo.lock$''' 6 | ] -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by the Apollo SecOps team 2 | # Please customize this file as needed prior to merging. 3 | 4 | * @apollographql/fed-core 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["apollo-federation-types", "apollo-composition"] 3 | resolver = "2" 4 | 5 | [workspace.dependencies] 6 | apollo-compiler = "1.30.0" 7 | apollo-federation = "=2.8.2" 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | Each crate has its own changelog: 4 | 5 | - [apollo-composition](./apollo-composition/CHANGELOG.md) 6 | - [apollo-federation-types](./apollo-federation-types/CHANGELOG.md) 7 | -------------------------------------------------------------------------------- /apollo-composition/README.md: -------------------------------------------------------------------------------- 1 | # Composition 2 | 3 | This crate orchestrates composition between the TypeScript `@apollo/composition` and the Rust `apollo-federation`. 4 | 5 | Consumers are expected to bring their own implementation of the TypeScript component. 6 | -------------------------------------------------------------------------------- /apollo-federation-types/README.md: -------------------------------------------------------------------------------- 1 |
2 |

apollo-federation-types

3 | 4 |

5 | Rust types used by Apollo's Rover CLI. 6 |

7 |
8 | 9 | This crate is not intended for public consumption. 10 | -------------------------------------------------------------------------------- /apollo-federation-types/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "build")] 2 | pub mod rover; 3 | 4 | #[cfg(feature = "build_plugin")] 5 | pub mod build_plugin; 6 | 7 | #[cfg(feature = "config")] 8 | pub mod config; 9 | 10 | #[cfg(feature = "composition")] 11 | pub mod composition; 12 | pub mod javascript; 13 | 14 | pub(crate) type UncaughtJson = std::collections::BTreeMap; 15 | -------------------------------------------------------------------------------- /apollo-federation-types/src/config/mod.rs: -------------------------------------------------------------------------------- 1 | mod config_error; 2 | mod subgraph; 3 | mod supergraph; 4 | mod version; 5 | 6 | pub use config_error::ConfigError; 7 | pub use version::{FederationVersion, PluginVersion, RouterVersion}; 8 | pub type ConfigResult = std::result::Result; 9 | pub use subgraph::{SchemaSource, SubgraphConfig}; 10 | pub use supergraph::SupergraphConfig; 11 | -------------------------------------------------------------------------------- /apollo-federation-types/src/build_plugin/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module is internal shared types between several other packages 2 | 3 | mod build_message; 4 | mod plugin_result; 5 | 6 | pub use build_message::BuildMessage; 7 | pub use build_message::BuildMessageLevel; 8 | pub use build_message::BuildMessageLocation; 9 | pub use build_message::BuildMessagePoint; 10 | pub use plugin_result::PluginFailureReason; 11 | pub use plugin_result::PluginResult; 12 | -------------------------------------------------------------------------------- /apollo-composition/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "apollo-composition" 3 | version = "0.4.1" 4 | license = "Elastic-2.0" 5 | edition = "2021" 6 | authors = ["Apollo Developers "] 7 | description = "Internal package used to create Apollo products" 8 | readme = "README.md" 9 | repository = "https://github.com/apollographql/federation-rs/" 10 | 11 | [dependencies] 12 | apollo-compiler = { workspace = true } 13 | apollo-federation = { workspace = true } 14 | apollo-federation-types = { version = "0.16.1", path = "../apollo-federation-types", features = [ 15 | "composition", 16 | ] } 17 | -------------------------------------------------------------------------------- /macos-entitlements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.allow-jit 6 | 7 | com.apple.security.cs.allow-unsigned-executable-memory 8 | 9 | com.apple.security.cs.disable-executable-page-protection 10 | 11 | com.apple.security.cs.allow-dyld-environment-variables 12 | 13 | com.apple.security.cs.disable-library-validation 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /apollo-composition/RELEASE_CHECKLIST.md: -------------------------------------------------------------------------------- 1 | # Release Checklist 2 | 3 | This is a list of the things that need to happen when publishing `apollo-composition`. 4 | 5 | ## Build a Release 6 | 7 | ### Create and merge your release PR 8 | 9 | 1. Create a branch 10 | 2. Update the `CHANGELOG.md` file in this directory. This is done completely by hand today. 11 | 3. Update the version of `apollo-composition` in `Cargo.toml` 12 | 4. Push up a commit and open a PR to `main` 13 | 5. Wait for tests to pass on the PR, then merge to `main` 14 | 15 | ### Build and tag release 16 | 17 | 1. Once merged, run `git switch main && git pull` 18 | 2. Create and push a tag called `apollo-composition@v` where `` is the version you just updated 19 | in `Cargo.toml` 20 | 3. Wait for CI to build and publish `apollo-composition` to crates.io. 21 | -------------------------------------------------------------------------------- /apollo-federation-types/RELEASE_CHECKLIST.md: -------------------------------------------------------------------------------- 1 | # Release Checklist 2 | 3 | This is a list of the things that need to happen when publishing `apollo-federation-types`. 4 | 5 | ## Build a Release 6 | 7 | ### Create and merge your release PR 8 | 9 | 1. Create a branch 10 | 2. Update the `CHANGELOG.md` file in this directory. This is done completely by hand today. 11 | 3. Update the version of `apollo-federation-types` in `Cargo.toml` 12 | 4. Push up a commit and open a PR to `main` 13 | 5. Wait for tests to pass on the PR, then merge to `main` 14 | 15 | ### Build and tag release 16 | 17 | 1. Once merged, run `git switch main && git pull` 18 | 2. Create and push a tag called `apollo-federation-types@v` where `` is the version you just updated 19 | in `Cargo.toml` 20 | 3. Wait for CI to build and publish `apollo-federation-types` to crates.io. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `federation-rs` 2 | 3 | This repository is responsible for all the TypeScript <--> Rust interop. Currently 4 | this includes composition and query planning. 5 | 6 | ## Branch Strategy 7 | 8 | `main` is for the latest stable federation v2.x release. We can create support branches for older versions of federation 9 | (like `support/v1`). 10 | 11 | ## Crates 12 | 13 | Each crate listed here has their own README with much more information than what's here. 14 | 15 | ### `apollo-composition` 16 | 17 | Bridges the gap between the JavaScript [federation](https://github.com/apollographql/federation) and the Rust 18 | [apollo-federation](https://github.com/apollographql/router) libraries for composition. 19 | 20 | ### `apollo-federation-types` 21 | 22 | The `apollo-federation-types` crate has shared types used for both Rover and Apollo GraphOS services, primarily 23 | around the composition process. 24 | -------------------------------------------------------------------------------- /apollo-federation-types/src/config/config_error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | pub enum ConfigError { 5 | #[error("Could not parse supergraph config: {message}.")] 6 | InvalidConfiguration { message: String }, 7 | 8 | #[error("File \"{file_path}\" not found: {message}.")] 9 | MissingFile { file_path: String, message: String }, 10 | 11 | #[error("Config for subgraph(s) {subgraph_names} are not fully resolved. name, routing_url, and sdl must be present.")] 12 | SubgraphsNotResolved { subgraph_names: String }, 13 | 14 | #[error("No subgraphs were found in the supergraph config.")] 15 | NoSubgraphsFound, 16 | } 17 | 18 | impl ConfigError { 19 | pub fn message(&self) -> String { 20 | self.to_string() 21 | } 22 | 23 | pub fn code(&self) -> Option { 24 | // TODO: Eventually add codes to these? 25 | None 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /apollo-federation-types/src/rover/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module contains the interface between Rover and its `supergraph` binaries. 2 | 3 | pub use error::{BuildError, BuildErrorType, BuildErrors}; 4 | pub use hint::BuildHint; 5 | pub use output::BuildOutput; 6 | 7 | use crate::build_plugin::{BuildMessageLevel, PluginFailureReason, PluginResult}; 8 | 9 | mod error; 10 | mod hint; 11 | mod output; 12 | 13 | /// The type representing the result of a supergraph build (for any version) 14 | pub type BuildResult = Result; 15 | 16 | impl From for BuildResult { 17 | fn from(value: PluginResult) -> Self { 18 | let mut hints = Vec::new(); 19 | let mut errors = Vec::new(); 20 | for message in value.build_messages { 21 | match message.level { 22 | BuildMessageLevel::Error => { 23 | errors.push(BuildError::from(message)); 24 | } 25 | _ => { 26 | hints.push(BuildHint::from(message)); 27 | } 28 | } 29 | } 30 | value 31 | .result 32 | .map(|supergraph_sdl| BuildOutput { 33 | supergraph_sdl, 34 | hints, 35 | other: Default::default(), 36 | }) 37 | .map_err(|reason| BuildErrors { 38 | build_errors: errors, 39 | is_config: reason == PluginFailureReason::Config, 40 | }) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /apollo-federation-types/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Apollo Developers "] 3 | description = """ 4 | apollo-federation-types contains types used by plugins for the Rover CLI 5 | """ 6 | edition = "2021" 7 | license = "MIT" 8 | name = "apollo-federation-types" 9 | readme = "README.md" 10 | repository = "https://github.com/apollographql/federation-rs/" 11 | version = "0.16.1" 12 | 13 | [features] 14 | default = ["config", "build", "build_plugin"] 15 | 16 | build = ["serde_json"] 17 | build_plugin = ["serde_json"] 18 | composition = ["apollo-compiler"] 19 | config = ["log", "thiserror", "serde_yaml", "url", "serde_with"] 20 | json_schema = ["schemars"] 21 | 22 | [dependencies] 23 | # only used for composition 24 | apollo-compiler = { workspace = true, optional = true } 25 | apollo-federation = { workspace = true } 26 | 27 | # config and build dependencies 28 | serde = { version = "1", features = ["derive"] } 29 | schemars = { version = "1", optional = true, features = ["url2"] } 30 | 31 | # config-only dependencies 32 | log = { version = "0.4", optional = true } 33 | semver = { version = "1", features = ["serde"] } 34 | serde_with = { version = "3", default-features = false, features = [ 35 | "macros", 36 | ], optional = true } 37 | serde_yaml = { version = "0.8", optional = true } 38 | thiserror = { version = "1", optional = true } 39 | url = { version = "2", features = ["serde"], optional = true } 40 | 41 | # build-only dependencies 42 | serde_json = { version = "1", optional = true } 43 | 44 | [dev-dependencies] 45 | assert_fs = "1" 46 | rstest = "0.21.0" 47 | serde_json = "1" 48 | serde_yaml = "0.8" 49 | -------------------------------------------------------------------------------- /apollo-composition/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.4.1 4 | 5 | - Update `apollo-federation` dependency to v2.8.2 (from v2.8.0) 6 | 7 | ## 0.4.0 8 | 9 | - Update `apollo-federation` dependency to v2.8.0 (from v2.7.0) 10 | - Add validation support for `@cacheTag` directive 11 | 12 | ## 0.3.5 13 | 14 | - Update `apollo-federation` dependency crate to v2.7.0 (from v2.7.0) 15 | - Update `apollo-compiler` dependency crate to v1.30.0 (from v1.28.0) 16 | 17 | ## 0.3.4 18 | 19 | - Update `apollo-federation` dependency crate to v2.5.0 (from v2.4.0) 20 | 21 | ## 0.3.3 22 | 23 | - Update `apollo-federation` dependency crate to v2.4.0 (from v2.3.0) 24 | 25 | ## 0.3.1 26 | 27 | - Experimental support for incremental composition steps 28 | 29 | ## 0.3.0 30 | 31 | - Update to `apollo-federation` 2.11 32 | 33 | ## 0.2.6 34 | 35 | - Update to `apollo-federation-types` 0.15.3 36 | 37 | ## 0.2.5 38 | 39 | - Update to `apollo-federation` 2.0.0 40 | 41 | ## 0.2.2 42 | 43 | - Prepend `[subgraph_name]` to Issue messages for better error attribution. 44 | 45 | ## 0.2.0 46 | 47 | - Pin `apollo-federation` to 2.0.0-preview.4 to prevent future breaking changes 48 | - Move `Issue`, `Severity`, and `SubgraphLocation` to new `apollo_federation_types::composition` module so some 49 | consumers can avoid pulling in extra dependencies. Requires `apollo_federation_types` 50 | 51 | ## 0.1.6 52 | 53 | - Update to `apollo-federation` 2.0.0-preview.3 54 | 55 | ## 0.1.5 56 | 57 | - [#590](https://github.com/apollographql/federation-rs/pull/590) Fix 58 | deserialization of `GraphQLError` nodes. 59 | - Update to `apollo-federation` 2.0.0-preview.1 60 | 61 | ## 0.1.4 62 | 63 | - Update to `apollo-federation` 2.0.0-preview.0 64 | 65 | ## 0.1.3 66 | 67 | - [#586](https://github.com/apollographql/federation-rs/pull/586) Make 68 | `SubgraphLocation.subgraph` an `Option`. For now, composition errors can have 69 | no attributed subgraph. 70 | - [#583](https://github.com/apollographql/federation-rs/pull/583) Remove 71 | connectors warning. 72 | 73 | ## 0.1.2 74 | 75 | - Updated dependencies 76 | -------------------------------------------------------------------------------- /.github/renovate.json5: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base", 4 | ":semanticCommits", 5 | ], 6 | // Keep automerge off for now, while we validate this works. 7 | "automerge": false, 8 | "ignorePaths": [ 9 | // Add the Renovate config:base defaults 10 | "**/node_modules/**", 11 | "**/bower_components/**", 12 | "**/vendor/**", 13 | "**/examples/**", 14 | "**/fixtures/**", 15 | "**/test/**", 16 | "**/tests/**", 17 | ], 18 | "packageRules": [ 19 | // Bunch up all non-major npm dependencies into a single PR. In the common case 20 | // where the upgrades apply cleanly, this causes less noise and is resolved faster 21 | // than starting a bunch of upgrades in parallel for what may turn out to be 22 | // a suite of related packages all released at once. 23 | // 24 | // Since too much in the Rust ecosystem is pre-1.0, we make an exception here. 25 | { 26 | "matchCurrentVersion": "< 1.0.0", 27 | "separateMinorPatch": true, 28 | "matchManagers": [ 29 | "cargo" 30 | ], 31 | "minor": { 32 | "groupName": "cargo pre-1.0 packages", 33 | "groupSlug": "cargo-all-pre-1.0", 34 | "automerge": false, 35 | // eventually true? 36 | }, 37 | "patch": { 38 | "groupName": "cargo pre-1.0 packages", 39 | "groupSlug": "cargo-all-pre-1.0", 40 | "automerge": false, 41 | // eventually true? 42 | } 43 | }, 44 | // cargo by itself for non-major >= 1.0.0 45 | { 46 | "matchCurrentVersion": ">= 1.0.0", 47 | "matchManagers": [ 48 | "cargo" 49 | ], 50 | "matchUpdateTypes": [ 51 | "minor", 52 | "patch", 53 | "pin", 54 | "digest" 55 | ], 56 | "groupName": "all cargo non-major packages >= 1.0", 57 | "groupSlug": "cargo-all-non-major-gte-1.0", 58 | "automerge": false, 59 | }, 60 | // CentOS 8 is EOL, but 7 lives on. 61 | { 62 | "matchPackageNames": [ 63 | "centos" 64 | ], 65 | "allowedVersions": "7.x" 66 | } 67 | ] 68 | } 69 | -------------------------------------------------------------------------------- /apollo-federation-types/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | Not every version is listed here because versions before 0.14.0 did not have a changelog. 4 | 5 | ## 0.16.1 6 | 7 | - Update `apollo-federation` dependency to v2.8.2 (from v2.8.0) 8 | 9 | ## 0.16.0 10 | 11 | - Update `apollo-federation` dependency to v2.8.0 (from v2.7.0) 12 | 13 | ## 0.15.10 14 | 15 | ### Features 16 | 17 | - Update `apollo-federation` dependency crate to v2.7.0 (from v2.7.0) 18 | - Update `apollo-compiler` dependency crate to v1.30.0 (from v1.28.0) 19 | 20 | ## 0.15.9 21 | 22 | ### Features 23 | 24 | - Update `apollo-federation` dependency crate to v2.5.0 (from v2.4.0) 25 | 26 | ## 0.15.8 27 | 28 | ### Features 29 | 30 | - Update `schemars` dependency crate to v1 (from v0.8.21) 31 | 32 | ## 0.15.7 33 | 34 | ### Features 35 | 36 | - Update `apollo-federation` dependency crate to v2.4.0 (from v2.3.0) 37 | 38 | ## 0.15.5 39 | 40 | ### Features 41 | 42 | - Derive Hash for `Issue` and `CompositionHint` 43 | 44 | ## 0.15.4 45 | 46 | ### Features 47 | 48 | - Experimental support for incremental composition steps 49 | 50 | ## 0.15.3 51 | 52 | ### Features 53 | 54 | - In `RouterVersion`, rename `Latest` to `LastestOne` and add `LatestTwo` 55 | 56 | ## 0.15.2 57 | 58 | ### Features 59 | 60 | - Prepend `[subgraph_name]` to Issue messages for better error attribution. 61 | 62 | ## 0.15.1 63 | 64 | ### Features 65 | 66 | - Added new `composition` module behind the `composition` Cargo feature for types related to composition (previously in the `apollo-composition` crate). 67 | 68 | ## 0.15.0 69 | 70 | ### Breaking changes 71 | 72 | - `GraphQLError.nodes` is now an `Option>` 73 | - All usages of `camino::Utf8PathBuf` have been replaced with `std::path::PathBuf` 74 | 75 | ### Features 76 | 77 | - A new `json_schema` feature derives the `schemars::JsonSchema` trait on `SupergraphConfig` and its sub-types. 78 | 79 | ## 0.14.1 - 2024-09-19 80 | 81 | ### Features 82 | 83 | - `impl FromIterator<(String, SubgraphConfig)> for SupergraphConfig` 84 | 85 | ## 0.14.0 - 2024-09-11 86 | 87 | ### Breaking changes 88 | 89 | - Removed `BuildErrorNode` in favor of `BuildMessageLocation`. 90 | - Removed `BuildErrorNodeLocationToken` 91 | - `BuildMessagePoint` now uses `usize` instead of `u32` 92 | - The `build` mod has been renamed `rover` to better represent the interface. 93 | - `SubgraphDefinition` is in the new `javascript` mod. 94 | - Removed `SubgraphDefinition::new` which was just a cloning wrapper around `pub` attributes 95 | 96 | ### Features 97 | 98 | - `impl From for BuildHint` 99 | - `impl From for BuildError` 100 | - `impl From for BuildResult` 101 | - Added a new `javascript` mod for types matching the `@apollo/composition` JavaScript package. 102 | -------------------------------------------------------------------------------- /apollo-federation-types/src/javascript/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module contains types matching those in the JavaScript `@apollo/composition` package. 2 | 3 | use apollo_federation::subgraph::typestate::{Initial, Subgraph, Validated}; 4 | use apollo_federation::subgraph::SubgraphError; 5 | use serde::{Deserialize, Serialize}; 6 | 7 | /// The `SubgraphDefinition` represents everything we need to know about a 8 | /// subgraph for its GraphQL runtime responsibilities. 9 | #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] 10 | pub struct SubgraphDefinition { 11 | /// The name of the subgraph. We use this name internally to 12 | /// in the representation of the composed schema and for designations 13 | /// within the human-readable QueryPlan. 14 | pub name: String, 15 | 16 | /// The routing/runtime URL where the subgraph can be found that will 17 | /// be able to fulfill the requests it is responsible for. 18 | pub url: String, 19 | 20 | /// The Schema Definition Language (SDL) containing the type definitions 21 | /// for a subgraph. 22 | pub sdl: String, 23 | } 24 | 25 | /// The structure returned by `validateSatisfiability` in `@apollo/composition` 26 | #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] 27 | pub struct SatisfiabilityResult { 28 | pub errors: Option>, 29 | pub hints: Option>, 30 | } 31 | 32 | #[derive(Debug, Clone, Eq, Hash, PartialEq, Deserialize, Serialize)] 33 | pub struct CompositionHint { 34 | pub message: String, 35 | pub nodes: Option>, 36 | pub definition: HintCodeDefinition, 37 | } 38 | 39 | #[derive(Debug, Clone, Eq, Hash, PartialEq, Deserialize, Serialize)] 40 | pub struct HintCodeDefinition { 41 | pub code: String, 42 | } 43 | 44 | #[derive(Debug, Clone, Eq, Hash, PartialEq, Deserialize, Serialize)] 45 | pub struct SubgraphASTNode { 46 | pub loc: Option, 47 | pub subgraph: Option, 48 | } 49 | 50 | #[derive(Debug, Clone, Eq, Hash, PartialEq, Deserialize, Serialize)] 51 | #[serde(rename_all = "camelCase")] 52 | pub struct Location { 53 | pub start_token: Token, 54 | pub end_token: Token, 55 | } 56 | 57 | #[derive(Debug, Clone, Eq, Hash, PartialEq, Deserialize, Serialize)] 58 | pub struct Token { 59 | pub column: Option, 60 | pub line: Option, 61 | } 62 | 63 | #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] 64 | pub struct GraphQLError { 65 | pub message: String, 66 | pub nodes: Option>, 67 | pub extensions: Option, 68 | } 69 | 70 | #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] 71 | pub struct GraphQLErrorExtensions { 72 | pub code: String, 73 | } 74 | 75 | impl TryFrom for Subgraph { 76 | type Error = SubgraphError; 77 | 78 | fn try_from(value: SubgraphDefinition) -> Result { 79 | Subgraph::parse(value.name.as_str(), value.url.as_str(), value.sdl.as_str()) 80 | } 81 | } 82 | 83 | impl From> for SubgraphDefinition { 84 | fn from(value: Subgraph) -> Self { 85 | SubgraphDefinition { 86 | sdl: value.schema_string(), 87 | name: value.name, 88 | url: value.url, 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /apollo-federation-types/src/rover/hint.rs: -------------------------------------------------------------------------------- 1 | use crate::build_plugin::{BuildMessage, BuildMessageLocation}; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | /// BuildHint contains helpful information that pertains to a build 5 | /// New fields added to this struct must be optional in order to maintain 6 | /// backwards compatibility with old versions of Rover. 7 | #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] 8 | #[serde(rename_all = "camelCase")] 9 | pub struct BuildHint { 10 | /// The message of the hint 11 | pub message: String, 12 | 13 | /// The code of the hint, this is an Option to maintain backwards compatibility. 14 | pub code: Option, 15 | 16 | pub nodes: Option>, 17 | 18 | pub omitted_nodes_count: Option, 19 | 20 | /// Other untyped JSON included in the build hint. 21 | #[serde(flatten)] 22 | pub other: crate::UncaughtJson, 23 | } 24 | 25 | impl From for BuildHint { 26 | fn from(message: BuildMessage) -> Self { 27 | BuildHint { 28 | message: message.message, 29 | code: message.code, 30 | other: message.other, 31 | nodes: Some(message.locations), 32 | omitted_nodes_count: None, 33 | } 34 | } 35 | } 36 | 37 | impl BuildHint { 38 | pub fn new( 39 | message: String, 40 | code: String, 41 | nodes: Option>, 42 | omitted_nodes_count: Option, 43 | ) -> Self { 44 | Self { 45 | message, 46 | code: Some(code), 47 | nodes, 48 | omitted_nodes_count, 49 | other: crate::UncaughtJson::new(), 50 | } 51 | } 52 | } 53 | 54 | #[cfg(test)] 55 | mod tests { 56 | use serde_json::{json, Value}; 57 | 58 | use super::*; 59 | 60 | #[test] 61 | fn it_can_serialize() { 62 | let msg = "hint".to_string(); 63 | let code = "hintCode".to_string(); 64 | let expected_json = 65 | json!({ "message": &msg, "code": &code, "nodes": null, "omittedNodesCount": null }); 66 | let actual_json = serde_json::to_value(&BuildHint::new(msg, code, None, None)).unwrap(); 67 | assert_eq!(expected_json, actual_json) 68 | } 69 | 70 | #[test] 71 | fn it_can_deserialize() { 72 | let msg = "hint".to_string(); 73 | let code = "hintCode".to_string(); 74 | let actual_struct = serde_json::from_str( 75 | &json!({ "message": &msg, "code": &code, "nodes": null, "omittedNodesCount": 12 }) 76 | .to_string(), 77 | ) 78 | .unwrap(); 79 | let expected_struct = BuildHint::new(msg, code, None, Some(12)); 80 | assert_eq!(expected_struct, actual_struct); 81 | } 82 | 83 | #[test] 84 | fn it_can_deserialize_even_with_unknown_fields() { 85 | let msg = "hint".to_string(); 86 | let code = "hintCode".to_string(); 87 | let unexpected_key = "this-would-never-happen".to_string(); 88 | let unexpected_value = "but-maybe-something-else-more-reasonable-would".to_string(); 89 | let actual_struct = serde_json::from_str( 90 | &json!({ "message": &msg, "code": &code, &unexpected_key: &unexpected_value }) 91 | .to_string(), 92 | ) 93 | .unwrap(); 94 | let mut expected_struct = BuildHint::new(msg, code, None, None); 95 | expected_struct 96 | .other 97 | .insert(unexpected_key, Value::String(unexpected_value)); 98 | assert_eq!(expected_struct, actual_struct); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /apollo-federation-types/src/config/subgraph.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::collections::HashMap; 3 | use std::path::PathBuf; 4 | use url::Url; 5 | 6 | /// Config for a single [subgraph](https://www.apollographql.com/docs/federation/subgraphs/) 7 | #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] 8 | #[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))] 9 | pub struct SubgraphConfig { 10 | /// The routing URL for the subgraph. 11 | /// This will appear in supergraph SDL and 12 | /// instructs the graph router to send all requests 13 | /// for this subgraph to this URL. 14 | pub routing_url: Option, 15 | 16 | /// The location of the subgraph's SDL 17 | pub schema: SchemaSource, 18 | } 19 | 20 | impl SubgraphConfig { 21 | /// Returns SDL from the configuration file if it exists. 22 | /// Returns None if the configuration does not include raw SDL. 23 | pub fn get_sdl(&self) -> Option { 24 | if let SchemaSource::Sdl { sdl } = &self.schema { 25 | Some(sdl.to_owned()) 26 | } else { 27 | None 28 | } 29 | } 30 | } 31 | 32 | /// Options for getting SDL: 33 | /// the graph registry, a file, or an introspection URL. 34 | /// 35 | /// NOTE: Introspection strips all comments and directives 36 | /// from the SDL. 37 | #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] 38 | // this is untagged, meaning its fields will be flattened into the parent 39 | // struct when de/serialized. There is no top level `schema_source` 40 | // in the configuration. 41 | #[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))] 42 | #[serde(untagged)] 43 | pub enum SchemaSource { 44 | File { 45 | file: PathBuf, 46 | }, 47 | SubgraphIntrospection { 48 | subgraph_url: Url, 49 | introspection_headers: Option>, 50 | }, 51 | Subgraph { 52 | graphref: String, 53 | subgraph: String, 54 | }, 55 | Sdl { 56 | sdl: String, 57 | }, 58 | } 59 | 60 | #[cfg(test)] 61 | mod test_schema_source { 62 | use crate::config::SchemaSource; 63 | use serde_yaml::from_str; 64 | 65 | #[test] 66 | fn test_file() { 67 | let yaml = "file: some/path.thing"; 68 | let source: SchemaSource = from_str(yaml).unwrap(); 69 | let expected = SchemaSource::File { 70 | file: "some/path.thing".into(), 71 | }; 72 | assert_eq!(source, expected); 73 | } 74 | 75 | #[test] 76 | fn test_subgraph_introspection_no_headers() { 77 | let yaml = "subgraph_url: https://example.com/graphql"; 78 | let source: SchemaSource = from_str(yaml).unwrap(); 79 | let expected = SchemaSource::SubgraphIntrospection { 80 | subgraph_url: "https://example.com/graphql".parse().unwrap(), 81 | introspection_headers: None, 82 | }; 83 | assert_eq!(source, expected); 84 | } 85 | 86 | #[test] 87 | fn test_subgraph_introspection_with_headers() { 88 | let yaml = r#" 89 | subgraph_url: https://example.com/graphql 90 | introspection_headers: 91 | Router-Authorization: ${env.HELLO_TESTS} 92 | "#; 93 | let source: SchemaSource = from_str(yaml).unwrap(); 94 | let mut expected_headers = std::collections::HashMap::new(); 95 | expected_headers.insert( 96 | "Router-Authorization".to_string(), 97 | "${env.HELLO_TESTS}".to_string(), 98 | ); 99 | let expected = SchemaSource::SubgraphIntrospection { 100 | subgraph_url: "https://example.com/graphql".parse().unwrap(), 101 | introspection_headers: Some(expected_headers), 102 | }; 103 | assert_eq!(source, expected); 104 | } 105 | 106 | #[test] 107 | fn test_subgraph() { 108 | let yaml = r#" 109 | graphref: my-graph@current 110 | subgraph: my-subgraph 111 | "#; 112 | let source: SchemaSource = from_str(yaml).unwrap(); 113 | let expected = SchemaSource::Subgraph { 114 | graphref: "my-graph@current".to_string(), 115 | subgraph: "my-subgraph".to_string(), 116 | }; 117 | assert_eq!(source, expected); 118 | } 119 | 120 | #[test] 121 | fn test_sdl() { 122 | let yaml = r#" 123 | sdl: | 124 | type Query { 125 | hello: String 126 | }"#; 127 | let source: SchemaSource = from_str(yaml).unwrap(); 128 | let expected = SchemaSource::Sdl { 129 | sdl: "type Query {\n hello: String\n}".to_string(), 130 | }; 131 | assert_eq!(source, expected); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 Apollo Graph, Inc. 2 | 3 | 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. 4 | 5 | -------------------------------------------------------------------------------- 6 | 7 | Elastic License 2.0 8 | 9 | ## Acceptance 10 | 11 | By using the software, you agree to all of the terms and conditions below. 12 | 13 | ## Copyright License 14 | 15 | The licensor grants you a non-exclusive, royalty-free, worldwide, 16 | non-sublicensable, non-transferable license to use, copy, distribute, make 17 | available, and prepare derivative works of the software, in each case subject to 18 | the limitations and conditions below. 19 | 20 | ## Limitations 21 | 22 | You may not provide the software to third parties as a hosted or managed 23 | service, where the service provides users with access to any substantial set of 24 | the features or functionality of the software. 25 | 26 | You may not move, change, disable, or circumvent the license key functionality 27 | in the software, and you may not remove or obscure any functionality in the 28 | software that is protected by the license key. 29 | 30 | You may not alter, remove, or obscure any licensing, copyright, or other notices 31 | of the licensor in the software. Any use of the licensor’s trademarks is subject 32 | to applicable law. 33 | 34 | ## Patents 35 | 36 | The licensor grants you a license, under any patent claims the licensor can 37 | license, or becomes able to license, to make, have made, use, sell, offer for 38 | sale, import and have imported the software, in each case subject to the 39 | limitations and conditions in this license. This license does not cover any 40 | patent claims that you cause to be infringed by modifications or additions to 41 | the software. If you or your company make any written claim that the software 42 | infringes or contributes to infringement of any patent, your patent license for 43 | the software granted under these terms ends immediately. If your company makes 44 | such a claim, your patent license ends immediately for work on behalf of your 45 | company. 46 | 47 | ## Notices 48 | 49 | You must ensure that anyone who gets a copy of any part of the software from you 50 | also gets a copy of these terms. 51 | 52 | If you modify the software, you must include in any modified copies of the 53 | software prominent notices stating that you have modified the software. 54 | 55 | ## No Other Rights 56 | 57 | These terms do not imply any licenses other than those expressly granted in 58 | these terms. 59 | 60 | ## Termination 61 | 62 | If you use the software in violation of these terms, such use is not licensed, 63 | and your licenses will automatically terminate. If the licensor provides you 64 | with a notice of your violation, and you cease all violation of this license no 65 | later than 30 days after you receive that notice, your licenses will be 66 | reinstated retroactively. However, if you violate these terms after such 67 | reinstatement, any additional violation of these terms will cause your licenses 68 | to terminate automatically and permanently. 69 | 70 | ## No Liability 71 | 72 | *As far as the law allows, the software comes as is, without any warranty or 73 | condition, and the licensor will not be liable to you for any damages arising 74 | out of these terms or the use or nature of the software, under any kind of 75 | legal claim.* 76 | 77 | ## Definitions 78 | 79 | The **licensor** is the entity offering these terms, and the **software** is the 80 | software the licensor makes available under these terms, including any portion 81 | of it. 82 | 83 | **you** refers to the individual or entity agreeing to these terms. 84 | 85 | **your company** is any legal entity, sole proprietorship, or other kind of 86 | organization that you work for, plus all organizations that have control over, 87 | are under the control of, or are under common control with that 88 | organization. **control** means ownership of substantially all the assets of an 89 | entity, or the power to direct its management and policies by vote, contract, or 90 | otherwise. Control can be direct or indirect. 91 | 92 | **your licenses** are all the licenses granted to you for the software under 93 | these terms. 94 | 95 | **use** means anything you do with the software requiring one of your licenses. 96 | 97 | **trademark** means trademarks, service marks, and similar rights. 98 | 99 | -------------------------------------------------------------------------------- 100 | -------------------------------------------------------------------------------- /apollo-federation-types/src/rover/output.rs: -------------------------------------------------------------------------------- 1 | use crate::rover::BuildHint; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | /// BuildOutput contains information about the supergraph that was composed. 6 | /// New fields added to this struct must be optional in order to maintain 7 | /// backwards compatibility with old versions of Rover. 8 | #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] 9 | #[serde(rename_all = "camelCase")] 10 | pub struct BuildOutput { 11 | /// Supergraph SDL can be used to start a gateway instance. 12 | pub supergraph_sdl: String, 13 | 14 | /// Hints contain information about the composition and should be displayed. 15 | pub hints: Vec, 16 | 17 | /// Other untyped JSON included in the build output. 18 | #[serde(flatten)] 19 | pub other: crate::UncaughtJson, 20 | } 21 | 22 | impl BuildOutput { 23 | /// Create output containing only a supergraph schema 24 | pub fn new(supergraph_sdl: String) -> Self { 25 | Self::new_with_hints(supergraph_sdl, Vec::new()) 26 | } 27 | 28 | /// Create output containing a supergraph schema and some hints 29 | pub fn new_with_hints(supergraph_sdl: String, hints: Vec) -> Self { 30 | Self { 31 | supergraph_sdl, 32 | hints, 33 | other: crate::UncaughtJson::new(), 34 | } 35 | } 36 | } 37 | 38 | #[cfg(test)] 39 | mod tests { 40 | use serde_json::{json, Value}; 41 | 42 | use super::*; 43 | 44 | #[test] 45 | fn it_can_serialize_without_hints() { 46 | let sdl = "my-sdl".to_string(); 47 | let expected_json = json!({"supergraphSdl": &sdl, "hints": []}); 48 | let actual_json = serde_json::to_value(&BuildOutput::new(sdl)).unwrap(); 49 | assert_eq!(expected_json, actual_json) 50 | } 51 | 52 | #[test] 53 | fn it_can_serialize_with_hints() { 54 | let sdl = "my-sdl".to_string(); 55 | let hint_one = "hint-one".to_string(); 56 | let hint_two = "hint-two".to_string(); 57 | let code = "code".to_string(); 58 | let code2 = "code2".to_string(); 59 | let expected_json = json!({"supergraphSdl": &sdl, "hints": [{"message": &hint_one, "code": &code, "nodes": null, "omittedNodesCount": null}, {"message": &hint_two, "code": &code2, "nodes": null, "omittedNodesCount": null}]}); 60 | let actual_json = serde_json::to_value(BuildOutput::new_with_hints( 61 | sdl.to_string(), 62 | vec![ 63 | BuildHint::new(hint_one, code, None, None), 64 | BuildHint::new(hint_two, code2, None, None), 65 | ], 66 | )) 67 | .unwrap(); 68 | assert_eq!(expected_json, actual_json) 69 | } 70 | 71 | #[test] 72 | fn it_can_deserialize_without_hints() { 73 | let sdl = "my-sdl".to_string(); 74 | let actual_struct = 75 | serde_json::from_str(&json!({"supergraphSdl": &sdl, "hints": []}).to_string()).unwrap(); 76 | let expected_struct = BuildOutput::new(sdl); 77 | 78 | assert_eq!(expected_struct, actual_struct) 79 | } 80 | 81 | #[test] 82 | fn it_can_deserialize_with_hints() { 83 | let sdl = "my-sdl".to_string(); 84 | let hint_one = "hint-one".to_string(); 85 | let hint_two = "hint-two".to_string(); 86 | let code = "code".to_string(); 87 | let code2 = "code2".to_string(); 88 | let actual_struct = 89 | serde_json::from_str(&json!({"supergraphSdl": &sdl, "hints": [{"message": &hint_one, "code": &code}, {"message": &hint_two, "code": &code2}]}).to_string()) 90 | .unwrap(); 91 | let expected_struct = BuildOutput::new_with_hints( 92 | sdl, 93 | vec![ 94 | BuildHint::new(hint_one, code, None, None), 95 | BuildHint::new(hint_two, code2, None, None), 96 | ], 97 | ); 98 | 99 | assert_eq!(expected_struct, actual_struct) 100 | } 101 | 102 | #[test] 103 | fn it_can_deserialize_even_with_unknown_fields() { 104 | let sdl = "my-sdl".to_string(); 105 | let unexpected_key = "this-would-never-happen".to_string(); 106 | let unexpected_value = "but-maybe-something-else-more-reasonable-would".to_string(); 107 | let actual_struct = serde_json::from_str( 108 | &json!({"supergraphSdl": &sdl, "hints": [], &unexpected_key: &unexpected_value}) 109 | .to_string(), 110 | ) 111 | .unwrap(); 112 | let mut expected_struct = BuildOutput::new(sdl); 113 | expected_struct 114 | .other 115 | .insert(unexpected_key, Value::String(unexpected_value)); 116 | 117 | assert_eq!(expected_struct, actual_struct) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | # Our CircleCI dependencies 4 | orbs: 5 | rust: circleci/rust@1.8.0 6 | secops: apollo/circleci-secops-orb@2.0.7 7 | 8 | release: &release 9 | filters: 10 | branches: 11 | ignore: /.*/ 12 | tags: # To trigger a release, push a tag with the format `package-name@vversion`. Like `apollo-composition@v0.1.0` 13 | only: /(apollo-federation-types@v.*)|(apollo-composition@v.*)/ 14 | 15 | parameters: 16 | # we can't easily pin on osx 17 | linux_cmake_version: 18 | type: string 19 | default: '3.27.3' 20 | 21 | # The main workflows executed for federation-rs 22 | workflows: 23 | lint: 24 | jobs: 25 | - lint 26 | test: 27 | jobs: 28 | - test: 29 | name: Run cargo tests on << matrix.platform >> 30 | matrix: 31 | parameters: 32 | platform: [ amd_ubuntu, arm_ubuntu, arm_macos ] 33 | 34 | release: 35 | jobs: 36 | - test: 37 | name: Run cargo tests on << matrix.platform >> 38 | matrix: 39 | parameters: 40 | platform: [ amd_ubuntu, arm_ubuntu, arm_macos ] 41 | <<: *release 42 | 43 | - publish_release: 44 | name: Publish to crates.io 45 | requires: 46 | - "Run cargo tests on amd_ubuntu" 47 | - "Run cargo tests on arm_ubuntu" 48 | - "Run cargo tests on arm_macos" 49 | <<: *release 50 | 51 | security-scans: 52 | jobs: 53 | - secops/gitleaks: 54 | context: 55 | - platform-docker-ro 56 | - github-orb 57 | - secops-oidc 58 | git-base-revision: <<#pipeline.git.base_revision>><><> 59 | git-revision: << pipeline.git.revision >> 60 | 61 | - secops/semgrep: 62 | context: 63 | - secops-oidc 64 | - github-orb 65 | git-base-revision: <<#pipeline.git.base_revision>><><> 66 | jobs: 67 | lint: 68 | executor: amd_ubuntu 69 | steps: 70 | - checkout 71 | - install_system_deps: 72 | platform: amd_ubuntu 73 | - run: 74 | name: Check Rust formatting 75 | command: cargo fmt --all -- --check 76 | - run: 77 | name: Check Rust lints 78 | command: cargo clippy --all-features -- -D warnings 79 | 80 | test: 81 | parameters: 82 | platform: 83 | type: executor 84 | executor: << parameters.platform >> 85 | steps: 86 | - checkout 87 | - install_system_deps: 88 | platform: << parameters.platform >> 89 | - run: 90 | name: Run cargo tests 91 | command: cargo test 92 | 93 | publish_release: 94 | executor: minimal_linux 95 | steps: 96 | - checkout 97 | - rust/install 98 | - run: 99 | name: Publish to crates.io 100 | command: | 101 | # The tag looks like `@v`, but cargo wants `@` 102 | CARGO_VERSION=$(echo $CIRCLE_TAG | sed 's/@v/@/') 103 | cargo publish -p $CARGO_VERSION 104 | 105 | # The machines we use to run our workflows on 106 | executors: 107 | arm_macos: &arm_macos_executor 108 | macos: 109 | xcode: "15.4.0" 110 | resource_class: m4pro.medium 111 | environment: 112 | RUSTUP_TARGET: "aarch64-apple-darwin" 113 | APPLE_TEAM_ID: "YQK948L752" 114 | APPLE_USERNAME: "opensource@apollographql.com" 115 | MACOS_PRIMARY_BUNDLE_ID: com.apollographql.supergraph 116 | 117 | amd_ubuntu: 118 | machine: 119 | image: ubuntu-2004:current 120 | resource_class: xlarge 121 | environment: 122 | RUSTUP_TARGET: "x86_64-unknown-linux-gnu" 123 | arm_ubuntu: 124 | machine: 125 | image: ubuntu-2004:current 126 | resource_class: arm.large 127 | environment: 128 | RUSTUP_TARGET: "aarch64-unknown-linux-gnu" 129 | 130 | minimal_linux: 131 | docker: 132 | - image: cimg/base:stable 133 | resource_class: small 134 | environment: 135 | RUSTUP_TARGET: "x86_64-unknown-linux-gnu" 136 | 137 | # reusable command snippets can be referred to in any `steps` object 138 | commands: 139 | install_system_deps: 140 | parameters: 141 | platform: 142 | type: executor 143 | steps: 144 | - when: 145 | condition: 146 | equal: [ *arm_macos_executor, << parameters.platform >> ] 147 | steps: 148 | - run: 149 | name: Install CMake 150 | command: brew install cmake 151 | 152 | - install_rust_toolchain: 153 | platform: << parameters.platform >> 154 | 155 | install_rust_toolchain: 156 | parameters: 157 | platform: 158 | type: executor 159 | steps: 160 | - rust/install 161 | - run: 162 | name: Adds rust target 163 | command: rustup target add $RUSTUP_TARGET 164 | - run: 165 | name: Set default rustc version 166 | command: | 167 | rustup install stable 168 | rustup default stable 169 | -------------------------------------------------------------------------------- /apollo-federation-types/src/build_plugin/build_message.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] 4 | #[serde(rename_all = "SCREAMING_SNAKE_CASE")] 5 | #[non_exhaustive] 6 | pub enum BuildMessageLevel { 7 | Debug, 8 | Info, 9 | Warn, 10 | Error, 11 | } 12 | 13 | #[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq)] 14 | /// BuildLocation represents the location of a build message in the GraphQLDoucment 15 | /// New fields added to this struct must be optional in order to maintain 16 | /// backwards compatibility with old versions of Rover 17 | pub struct BuildMessageLocation { 18 | pub subgraph: Option, 19 | 20 | pub source: Option, 21 | 22 | pub start: Option, 23 | pub end: Option, 24 | 25 | #[serde(flatten)] 26 | pub other: crate::UncaughtJson, 27 | } 28 | 29 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] 30 | pub struct BuildMessagePoint { 31 | pub start: Option, 32 | pub end: Option, 33 | pub column: Option, 34 | pub line: Option, 35 | } 36 | 37 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] 38 | #[serde(rename_all = "camelCase")] 39 | /// BuildMessages contains the log output of a build 40 | /// New fields added to this struct must be optional in order to maintain 41 | /// backwards compatibility 42 | pub struct BuildMessage { 43 | pub level: BuildMessageLevel, 44 | pub message: String, 45 | pub step: Option, 46 | pub code: Option, 47 | pub locations: Vec, 48 | pub schema_coordinate: Option, 49 | 50 | #[serde(flatten)] 51 | pub other: crate::UncaughtJson, 52 | } 53 | 54 | impl BuildMessage { 55 | pub fn new_error(error_message: String, step: Option, code: Option) -> Self { 56 | BuildMessage { 57 | level: BuildMessageLevel::Error, 58 | message: error_message, 59 | step, 60 | code, 61 | locations: vec![], 62 | schema_coordinate: None, 63 | other: crate::UncaughtJson::new(), 64 | } 65 | } 66 | } 67 | 68 | #[cfg(test)] 69 | mod tests { 70 | use serde_json::{json, Value}; 71 | 72 | use crate::build_plugin::{ 73 | build_message::BuildMessageLocation, BuildMessage, BuildMessageLevel, 74 | }; 75 | 76 | #[test] 77 | fn it_can_serialize_build_message() { 78 | let build_message: BuildMessage = BuildMessage { 79 | level: BuildMessageLevel::Debug, 80 | message: "wow".to_string(), 81 | step: None, 82 | code: None, 83 | locations: vec![], 84 | schema_coordinate: None, 85 | other: crate::UncaughtJson::new(), 86 | }; 87 | 88 | let actual_value: Value = serde_json::from_str( 89 | &serde_json::to_string(&build_message) 90 | .expect("Could not convert build errors to string"), 91 | ) 92 | .expect("Could not convert build error string to serde_json::Value"); 93 | 94 | let expected_value = json!({ 95 | "level": "DEBUG", 96 | "message": "wow", 97 | "step": null, 98 | "code": null, 99 | "locations": [], 100 | "schemaCoordinate": null, 101 | }); 102 | assert_eq!(actual_value, expected_value); 103 | } 104 | 105 | #[test] 106 | fn it_can_deserialize_even_with_unknown_fields() { 107 | let unexpected_key = "this-would-never-happen".to_string(); 108 | let unexpected_value = "but-maybe-something-else-more-reasonable-would".to_string(); 109 | let actual_struct = serde_json::from_str( 110 | &json!({ 111 | "level": "DEBUG", 112 | "message": "wow", 113 | "step": null, 114 | "code": null, 115 | "locations": [{&unexpected_key: &unexpected_value}], 116 | "schemaCoordinate": null, 117 | &unexpected_key: &unexpected_value, 118 | }) 119 | .to_string(), 120 | ) 121 | .unwrap(); 122 | 123 | let mut expected_struct: BuildMessage = BuildMessage { 124 | level: BuildMessageLevel::Debug, 125 | message: "wow".to_string(), 126 | step: None, 127 | code: None, 128 | locations: vec![BuildMessageLocation { 129 | subgraph: None, 130 | source: None, 131 | start: None, 132 | end: None, 133 | other: crate::UncaughtJson::new(), 134 | }], 135 | schema_coordinate: None, 136 | other: crate::UncaughtJson::new(), 137 | }; 138 | 139 | expected_struct.locations[0].other.insert( 140 | unexpected_key.clone(), 141 | Value::String(unexpected_value.clone()), 142 | ); 143 | 144 | expected_struct 145 | .other 146 | .insert(unexpected_key, Value::String(unexpected_value)); 147 | 148 | assert_eq!(expected_struct, actual_struct) 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /apollo-federation-types/src/build_plugin/plugin_result.rs: -------------------------------------------------------------------------------- 1 | use super::BuildMessage; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] 5 | #[serde(rename_all = "lowercase")] 6 | #[non_exhaustive] 7 | // This represents the reason for a build failrue 8 | pub enum PluginFailureReason { 9 | /// If the plugin failed because user inputs can't be built 10 | Build, 11 | /// If the configuration sent to the plugin is invalid 12 | Config, 13 | /// If the plugin failed for some internal reason 14 | InternalFailure, 15 | } 16 | 17 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] 18 | #[serde(rename_all = "camelCase")] 19 | /// PluginResult represents the output of a plugin execution 20 | /// New fields added to this struct must be optional in order to maintain 21 | /// backwards compatibility with old versions of Rover 22 | pub struct PluginResult { 23 | pub result: Result, 24 | pub build_messages: Vec, 25 | 26 | /// Other untyped JSON included in the build output. 27 | #[serde(flatten)] 28 | other: crate::UncaughtJson, 29 | } 30 | 31 | impl PluginResult { 32 | pub fn new( 33 | result: Result, 34 | build_messages: Vec, 35 | ) -> Self { 36 | Self { 37 | result, 38 | build_messages, 39 | other: crate::UncaughtJson::new(), 40 | } 41 | } 42 | 43 | pub fn new_failure( 44 | build_messages: Vec, 45 | execution_failure: PluginFailureReason, 46 | ) -> Self { 47 | Self { 48 | result: Err(execution_failure), 49 | build_messages, 50 | other: crate::UncaughtJson::new(), 51 | } 52 | } 53 | 54 | pub fn success_from_schema(schema: String) -> Self { 55 | Self { 56 | result: Ok(schema), 57 | build_messages: vec![], 58 | other: crate::UncaughtJson::new(), 59 | } 60 | } 61 | 62 | /** 63 | We may succed in Rust's perspective, but inside the JSON message may be isSuccess: false 64 | and buildMessages from composition telling us what went wrong. 65 | 66 | If there are, promote those to more semantic places in the output object. 67 | If there are not, cooool, pass the data along. 68 | */ 69 | pub fn from_plugin_result(result_json: &str) -> Self { 70 | let serde_json: Result = serde_json::from_str(result_json); 71 | serde_json.unwrap_or_else(|json_error| { 72 | PluginResult::new_failure( 73 | vec![BuildMessage::new_error( 74 | format!("Could not parse JSON from Rust. Received error {json_error}"), 75 | Some("PLUGIN_EXECUTION".to_string()), 76 | Some("PLUGIN_EXECUTION".to_string()), 77 | )], 78 | PluginFailureReason::InternalFailure, 79 | ) 80 | }) 81 | } 82 | 83 | pub fn to_json(&self) -> serde_json::Value { 84 | serde_json::json!(self) 85 | } 86 | } 87 | 88 | #[cfg(feature = "config")] 89 | impl From for PluginResult { 90 | fn from(config_error: crate::config::ConfigError) -> Self { 91 | PluginResult::new_failure( 92 | vec![BuildMessage::new_error( 93 | config_error.message(), 94 | Some("PLUGIN_CONFIGURATION".to_string()), 95 | config_error.code(), 96 | )], 97 | PluginFailureReason::Config, 98 | ) 99 | } 100 | } 101 | 102 | #[cfg(test)] 103 | mod tests { 104 | use serde_json::{json, Value}; 105 | 106 | use super::*; 107 | 108 | #[test] 109 | fn it_can_serialize_with_success() { 110 | let sdl = "my-sdl".to_string(); 111 | let expected_json = json!({"result":{ "Ok": &sdl}, "buildMessages": []}); 112 | let actual_json = serde_json::to_value(PluginResult { 113 | result: Ok(sdl), 114 | build_messages: vec![], 115 | other: crate::UncaughtJson::new(), 116 | }) 117 | .unwrap(); 118 | assert_eq!(expected_json, actual_json) 119 | } 120 | 121 | #[test] 122 | fn it_can_serialize_with_failure() { 123 | let expected_json = json!({ 124 | "result": {"Err": "build"}, 125 | "buildMessages": [], 126 | }); 127 | let actual_json = serde_json::to_value(PluginResult { 128 | result: Err(PluginFailureReason::Build), 129 | build_messages: vec![], 130 | other: crate::UncaughtJson::new(), 131 | }) 132 | .expect("Could not serialize PluginResult"); 133 | assert_eq!(expected_json, actual_json) 134 | } 135 | 136 | #[test] 137 | fn it_can_deserialize_with_success() { 138 | let sdl = "my-sdl".to_string(); 139 | let actual_struct = 140 | serde_json::from_str(&json!({"result": {"Ok": &sdl}, "buildMessages": []}).to_string()) 141 | .unwrap(); 142 | let expected_struct = PluginResult { 143 | result: Ok(sdl), 144 | build_messages: vec![], 145 | other: crate::UncaughtJson::new(), 146 | }; 147 | 148 | assert_eq!(expected_struct, actual_struct) 149 | } 150 | 151 | #[test] 152 | fn it_can_deserialize_with_failure() { 153 | let actual_struct = serde_json::from_str( 154 | &json!({"result": {"Err": "build"}, "buildMessages": []}).to_string(), 155 | ) 156 | .unwrap(); 157 | let expected_struct = PluginResult { 158 | result: Err(PluginFailureReason::Build), 159 | build_messages: vec![], 160 | other: crate::UncaughtJson::new(), 161 | }; 162 | 163 | assert_eq!(expected_struct, actual_struct) 164 | } 165 | 166 | #[test] 167 | fn it_can_deserialize_even_with_unknown_fields() { 168 | let sdl = "my-sdl".to_string(); 169 | let unexpected_key = "this-would-never-happen".to_string(); 170 | let unexpected_value = "but-maybe-something-else-more-reasonable-would".to_string(); 171 | let actual_struct = serde_json::from_str( 172 | &json!({"result": {"Ok": &sdl}, "buildMessages": [], &unexpected_key: &unexpected_value}).to_string(), 173 | ) 174 | .unwrap(); 175 | let mut expected_struct = PluginResult { 176 | result: Ok(sdl), 177 | build_messages: vec![], 178 | other: crate::UncaughtJson::new(), 179 | }; 180 | 181 | expected_struct 182 | .other 183 | .insert(unexpected_key, Value::String(unexpected_value)); 184 | 185 | assert_eq!(expected_struct, actual_struct) 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /apollo-federation-types/src/composition/mod.rs: -------------------------------------------------------------------------------- 1 | //! Types used with the `apollo-composition` crate 2 | 3 | use crate::build_plugin::{ 4 | BuildMessage, BuildMessageLevel, BuildMessageLocation, BuildMessagePoint, 5 | }; 6 | use crate::javascript::{CompositionHint, GraphQLError, SubgraphASTNode}; 7 | use crate::rover::{BuildError, BuildHint}; 8 | use apollo_compiler::parser::LineColumn; 9 | use apollo_federation::error::{CompositionError, FederationError}; 10 | use std::collections::HashSet; 11 | use std::fmt::{Display, Formatter}; 12 | use std::ops::Range; 13 | 14 | /// Group the types from the apollo-federation that can be ambiguous. 15 | mod native { 16 | pub(super) use apollo_federation::error::SubgraphLocation; 17 | pub(super) use apollo_federation::supergraph::CompositionHint; 18 | } 19 | 20 | /// Some issue the user should address. Errors block composition, warnings do not. 21 | #[derive(Clone, Debug, Hash, PartialEq)] 22 | pub struct Issue { 23 | pub code: String, 24 | pub message: String, 25 | pub locations: Vec, 26 | pub severity: Severity, 27 | } 28 | 29 | impl Display for Issue { 30 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 31 | // TODO include subgraph error location information once available 32 | write!(f, "{}: {}", self.code, self.message) 33 | } 34 | } 35 | 36 | impl From for Issue { 37 | fn from(error: GraphQLError) -> Issue { 38 | Issue { 39 | code: error 40 | .extensions 41 | .map(|extension| extension.code) 42 | .unwrap_or_default(), 43 | message: error.message, 44 | severity: Severity::Error, 45 | locations: error 46 | .nodes 47 | .unwrap_or_default() 48 | .into_iter() 49 | .filter_map(SubgraphLocation::from_ast) 50 | .collect(), 51 | } 52 | } 53 | } 54 | 55 | impl From for Issue { 56 | fn from(hint: CompositionHint) -> Issue { 57 | Issue { 58 | code: hint.definition.code, 59 | message: hint.message, 60 | severity: Severity::Warning, 61 | locations: hint 62 | .nodes 63 | .unwrap_or_default() 64 | .into_iter() 65 | .filter_map(SubgraphLocation::from_ast) 66 | .collect(), 67 | } 68 | } 69 | } 70 | 71 | impl From for Issue { 72 | fn from(error: BuildError) -> Issue { 73 | Issue { 74 | code: error 75 | .code 76 | .unwrap_or_else(|| "UNKNOWN_ERROR_CODE".to_string()), 77 | message: error.message.unwrap_or_else(|| "Unknown error".to_string()), 78 | locations: error 79 | .nodes 80 | .unwrap_or_default() 81 | .into_iter() 82 | .map(Into::into) 83 | .collect(), 84 | severity: Severity::Error, 85 | } 86 | } 87 | } 88 | 89 | impl From for Issue { 90 | fn from(hint: BuildHint) -> Issue { 91 | Issue { 92 | code: hint.code.unwrap_or_else(|| "UNKNOWN_HINT_CODE".to_string()), 93 | message: hint.message, 94 | locations: hint 95 | .nodes 96 | .unwrap_or_default() 97 | .into_iter() 98 | .map(Into::into) 99 | .collect(), 100 | severity: Severity::Warning, 101 | } 102 | } 103 | } 104 | 105 | // thrown from expand_connectors and Supergraph::parse 106 | impl From for Issue { 107 | fn from(error: FederationError) -> Self { 108 | let code = match &error { 109 | FederationError::SingleFederationError(err) => { 110 | err.code().definition().code().to_string() 111 | } 112 | _ => "UNKNOWN_ERROR_CODE".to_string(), 113 | }; 114 | Issue { 115 | code, 116 | // Composition failed due to an internal error, please report this: {} 117 | message: error.to_string(), 118 | locations: vec![], 119 | severity: Severity::Error, 120 | } 121 | } 122 | } 123 | 124 | impl From for Issue { 125 | fn from(error: CompositionError) -> Self { 126 | Issue { 127 | code: error.code().definition().code().to_string(), 128 | message: error.to_string(), 129 | locations: convert_subgraph_locations(error.locations().to_vec()), 130 | severity: Severity::Error, 131 | } 132 | } 133 | } 134 | 135 | impl From for Issue { 136 | fn from(hint: native::CompositionHint) -> Self { 137 | Issue { 138 | code: hint.code, 139 | message: hint.message, 140 | locations: convert_subgraph_locations(hint.locations), 141 | severity: Severity::Warning, 142 | } 143 | } 144 | } 145 | 146 | impl From for SubgraphLocation { 147 | fn from(location: native::SubgraphLocation) -> Self { 148 | SubgraphLocation { 149 | subgraph: Some(location.subgraph), 150 | range: Some(location.range), 151 | } 152 | } 153 | } 154 | 155 | fn convert_subgraph_locations( 156 | locations: impl IntoIterator, 157 | ) -> Vec { 158 | locations.into_iter().map(|loc| loc.into()).collect() 159 | } 160 | 161 | /// Rover and GraphOS expect messages to start with `[subgraph name]`. (They 162 | /// don't actually look at the `locations` field, sadly). This will prepend 163 | /// the subgraph name if there's exactly one. If there's more than one, it's 164 | /// probably a composition issue that's not attributable to a single subgraph, 165 | /// and GraphOS will show "[subgraph unknown]", which is also not correct. 166 | fn maybe_prepend_subgraph(message: &str, locations: &[SubgraphLocation]) -> String { 167 | if message.starts_with('[') { 168 | return message.to_string(); 169 | } 170 | let unique_subgraphs = locations 171 | .iter() 172 | .filter_map(|l| l.subgraph.as_ref()) 173 | .collect::>(); 174 | if unique_subgraphs.len() == 1 { 175 | format!( 176 | "[{}] {}", 177 | unique_subgraphs.iter().next().expect("qed"), 178 | message 179 | ) 180 | } else { 181 | message.to_string() 182 | } 183 | } 184 | 185 | impl From for BuildMessage { 186 | fn from(issue: Issue) -> Self { 187 | BuildMessage { 188 | level: issue.severity.into(), 189 | message: maybe_prepend_subgraph(&issue.message, &issue.locations), 190 | code: Some(issue.code.to_string()), 191 | locations: issue 192 | .locations 193 | .into_iter() 194 | .map(|location| location.into()) 195 | .collect(), 196 | schema_coordinate: None, 197 | step: None, 198 | other: Default::default(), 199 | } 200 | } 201 | } 202 | 203 | #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] 204 | pub enum Severity { 205 | Error, 206 | Warning, 207 | } 208 | 209 | impl From for BuildMessageLevel { 210 | fn from(severity: Severity) -> Self { 211 | match severity { 212 | Severity::Error => BuildMessageLevel::Error, 213 | Severity::Warning => BuildMessageLevel::Warn, 214 | } 215 | } 216 | } 217 | 218 | /// A location in a subgraph's SDL 219 | #[derive(Clone, Debug, Hash, PartialEq)] 220 | pub struct SubgraphLocation { 221 | /// This field is an Option to support the lack of subgraph names in 222 | /// existing composition errors. New composition errors should always 223 | /// include a subgraph name. 224 | pub subgraph: Option, 225 | pub range: Option>, 226 | } 227 | 228 | impl SubgraphLocation { 229 | fn from_ast(node: SubgraphASTNode) -> Option { 230 | Some(Self { 231 | subgraph: node.subgraph, 232 | range: node.loc.and_then(|node_loc| { 233 | Some(Range { 234 | start: LineColumn { 235 | line: node_loc.start_token.line?, 236 | column: node_loc.start_token.column?, 237 | }, 238 | end: LineColumn { 239 | line: node_loc.end_token.line?, 240 | column: node_loc.end_token.column?, 241 | }, 242 | }) 243 | }), 244 | }) 245 | } 246 | } 247 | 248 | impl From for BuildMessageLocation { 249 | fn from(location: SubgraphLocation) -> Self { 250 | BuildMessageLocation { 251 | subgraph: location.subgraph, 252 | start: location.range.as_ref().map(|range| BuildMessagePoint { 253 | line: Some(range.start.line), 254 | column: Some(range.start.column), 255 | start: None, 256 | end: None, 257 | }), 258 | end: location.range.as_ref().map(|range| BuildMessagePoint { 259 | line: Some(range.end.line), 260 | column: Some(range.end.column), 261 | start: None, 262 | end: None, 263 | }), 264 | source: None, 265 | other: Default::default(), 266 | } 267 | } 268 | } 269 | 270 | impl From for SubgraphLocation { 271 | fn from(location: BuildMessageLocation) -> Self { 272 | Self { 273 | subgraph: location.subgraph, 274 | range: location.start.and_then(|start| { 275 | let end = location.end?; 276 | Some(Range { 277 | start: LineColumn { 278 | line: start.line?, 279 | column: start.column?, 280 | }, 281 | end: LineColumn { 282 | line: end.line?, 283 | column: end.column?, 284 | }, 285 | }) 286 | }), 287 | } 288 | } 289 | } 290 | 291 | #[derive(Debug, Clone)] 292 | pub struct MergeResult { 293 | pub supergraph: String, 294 | pub hints: Vec, 295 | } 296 | 297 | #[cfg(test)] 298 | mod tests { 299 | use super::*; 300 | 301 | #[rstest::rstest] 302 | #[case("hello", &[], "hello")] 303 | #[case("hello", &[SubgraphLocation { subgraph: Some("subgraph".to_string()), range: None }], "[subgraph] hello")] 304 | #[case("[other] hello", &[SubgraphLocation { subgraph: Some("subgraph".to_string()), range: None }], "[other] hello")] 305 | #[case("hello", &[SubgraphLocation { subgraph: Some("subgraph".to_string()), range: None }, SubgraphLocation { subgraph: Some("other".to_string()), range: None }], "hello")] 306 | fn test_maybe_prepend_subgraph( 307 | #[case] message: &str, 308 | #[case] locations: &[SubgraphLocation], 309 | #[case] expected: &str, 310 | ) { 311 | assert_eq!(maybe_prepend_subgraph(message, locations), expected); 312 | } 313 | } 314 | -------------------------------------------------------------------------------- /apollo-federation-types/src/rover/error.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | error::Error, 3 | fmt::{self, Display}, 4 | }; 5 | 6 | use crate::build_plugin::{BuildMessage, BuildMessageLocation}; 7 | use serde::{Deserialize, Serialize}; 8 | 9 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] 10 | #[serde(rename_all = "camelCase")] 11 | pub struct BuildError { 12 | /// A message describing the build error. 13 | pub message: Option, 14 | 15 | /// A code describing the build error. 16 | pub code: Option, 17 | 18 | /// The type of build error. 19 | #[serde(rename = "type")] 20 | error_type: BuildErrorType, 21 | 22 | /// Other untyped JSON included in the build output. 23 | #[serde(flatten)] 24 | other: crate::UncaughtJson, 25 | 26 | pub nodes: Option>, 27 | 28 | omitted_nodes_count: Option, 29 | } 30 | 31 | impl From for BuildError { 32 | fn from(message: BuildMessage) -> Self { 33 | BuildError { 34 | message: Some(message.message), 35 | code: message.code, 36 | error_type: BuildErrorType::Composition, 37 | other: message.other, 38 | nodes: Some(message.locations), 39 | omitted_nodes_count: None, 40 | } 41 | } 42 | } 43 | 44 | impl BuildError { 45 | pub fn composition_error( 46 | code: Option, 47 | message: Option, 48 | nodes: Option>, 49 | omitted_nodes_count: Option, 50 | ) -> BuildError { 51 | BuildError::new( 52 | code, 53 | message, 54 | BuildErrorType::Composition, 55 | nodes, 56 | omitted_nodes_count, 57 | ) 58 | } 59 | 60 | pub fn config_error(code: Option, message: Option) -> BuildError { 61 | BuildError::new(code, message, BuildErrorType::Config, None, None) 62 | } 63 | 64 | fn new( 65 | code: Option, 66 | message: Option, 67 | error_type: BuildErrorType, 68 | nodes: Option>, 69 | omitted_nodes_count: Option, 70 | ) -> BuildError { 71 | let real_message = if code.is_none() && message.is_none() { 72 | Some("An unknown error occurred during the build.".to_string()) 73 | } else { 74 | message 75 | }; 76 | BuildError { 77 | code, 78 | message: real_message, 79 | error_type, 80 | other: crate::UncaughtJson::new(), 81 | nodes, 82 | omitted_nodes_count, 83 | } 84 | } 85 | 86 | pub fn get_message(&self) -> Option { 87 | self.message.clone() 88 | } 89 | 90 | pub fn get_code(&self) -> Option { 91 | self.code.clone() 92 | } 93 | 94 | pub fn get_type(&self) -> BuildErrorType { 95 | self.error_type.clone() 96 | } 97 | 98 | pub fn get_nodes(&self) -> Option> { 99 | self.nodes.clone() 100 | } 101 | 102 | pub fn get_omitted_nodes_count(&self) -> Option { 103 | self.omitted_nodes_count 104 | } 105 | } 106 | 107 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] 108 | #[serde(rename_all = "lowercase")] 109 | #[non_exhaustive] 110 | pub enum BuildErrorType { 111 | Composition, 112 | Config, 113 | } 114 | 115 | impl Display for BuildError { 116 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 117 | write!( 118 | f, 119 | "{}", 120 | self.code.as_ref().map_or("UNKNOWN", String::as_str) 121 | )?; 122 | if let Some(message) = &self.message { 123 | write!(f, ": {message}")?; 124 | } 125 | Ok(()) 126 | } 127 | } 128 | 129 | #[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq, Eq)] 130 | pub struct BuildErrors { 131 | pub build_errors: Vec, 132 | 133 | #[serde(skip)] 134 | pub is_config: bool, 135 | } 136 | 137 | impl BuildErrors { 138 | pub fn new() -> Self { 139 | BuildErrors { 140 | build_errors: Vec::new(), 141 | is_config: false, 142 | } 143 | } 144 | 145 | pub fn iter(&self) -> impl Iterator { 146 | self.build_errors.iter() 147 | } 148 | 149 | pub fn len(&self) -> usize { 150 | self.build_errors.len() 151 | } 152 | 153 | pub fn length_string(&self) -> String { 154 | let num_failures = self.build_errors.len(); 155 | if num_failures == 0 { 156 | unreachable!("No build errors were encountered while composing the supergraph."); 157 | } 158 | 159 | match num_failures { 160 | 1 => "1 build error".to_string(), 161 | _ => format!("{num_failures} build errors"), 162 | } 163 | } 164 | 165 | pub fn push(&mut self, error: BuildError) { 166 | if matches!(error.error_type, BuildErrorType::Config) { 167 | self.is_config = true; 168 | } 169 | self.build_errors.push(error); 170 | } 171 | 172 | pub fn is_empty(&self) -> bool { 173 | self.build_errors.is_empty() 174 | } 175 | 176 | pub fn extend(&mut self, other: BuildErrors) { 177 | self.build_errors.extend(other.build_errors); 178 | } 179 | } 180 | 181 | impl Display for BuildErrors { 182 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 183 | let num_failures = self.build_errors.len(); 184 | if num_failures == 0 185 | || (num_failures == 1 186 | && self.build_errors[0].code.is_none() 187 | && self.build_errors[0].message.is_none()) 188 | { 189 | writeln!(f, "Something went wrong! No build errors were recorded, but we also were unable to build a valid supergraph.")?; 190 | } else { 191 | for build_error in &self.build_errors { 192 | writeln!(f, "{build_error}")?; 193 | } 194 | } 195 | Ok(()) 196 | } 197 | } 198 | 199 | #[cfg(feature = "config")] 200 | impl From for BuildErrors { 201 | fn from(config_error: crate::config::ConfigError) -> Self { 202 | BuildErrors { 203 | build_errors: vec![BuildError::config_error( 204 | config_error.code(), 205 | Some(config_error.message()), 206 | )], 207 | is_config: true, 208 | } 209 | } 210 | } 211 | 212 | impl From> for BuildErrors { 213 | fn from(build_errors: Vec) -> Self { 214 | let is_config = build_errors 215 | .iter() 216 | .any(|e| matches!(e.error_type, BuildErrorType::Config)); 217 | BuildErrors { 218 | build_errors, 219 | is_config, 220 | } 221 | } 222 | } 223 | 224 | impl FromIterator for BuildErrors { 225 | fn from_iter>(iter: I) -> Self { 226 | let mut c = BuildErrors::new(); 227 | 228 | for i in iter { 229 | c.push(i); 230 | } 231 | 232 | c 233 | } 234 | } 235 | 236 | impl IntoIterator for BuildErrors { 237 | type Item = BuildError; 238 | type IntoIter = std::vec::IntoIter; 239 | 240 | fn into_iter(self) -> Self::IntoIter { 241 | self.build_errors.into_iter() 242 | } 243 | } 244 | 245 | impl Error for BuildError {} 246 | impl Error for BuildErrors {} 247 | 248 | #[cfg(test)] 249 | mod tests { 250 | use super::{BuildError, BuildErrors}; 251 | 252 | use crate::build_plugin::BuildMessageLocation; 253 | use serde_json::{json, Value}; 254 | 255 | #[test] 256 | fn it_supports_iter() { 257 | let build_errors: BuildErrors = vec![ 258 | BuildError::composition_error(None, Some("wow".to_string()), None, None), 259 | BuildError::composition_error( 260 | Some("BOO".to_string()), 261 | Some("boo".to_string()), 262 | None, 263 | None, 264 | ), 265 | ] 266 | .into(); 267 | 268 | let messages: Vec = build_errors 269 | .iter() 270 | .map(|e| e.get_message().unwrap()) 271 | .collect(); 272 | 273 | assert_eq!(messages, vec!["wow", "boo"]); 274 | } 275 | 276 | #[test] 277 | fn it_can_serialize_empty_errors() { 278 | let build_errors = BuildErrors::new(); 279 | assert_eq!( 280 | serde_json::to_string(&build_errors).expect("Could not serialize build errors"), 281 | json!({"build_errors": []}).to_string() 282 | ); 283 | } 284 | 285 | #[test] 286 | fn it_can_serialize_some_build_errors() { 287 | let error_node = BuildMessageLocation { 288 | subgraph: Some("foo".to_string()), 289 | ..Default::default() 290 | }; 291 | 292 | let build_errors: BuildErrors = vec![ 293 | BuildError::composition_error( 294 | None, 295 | Some("wow".to_string()), 296 | Some(vec![error_node.clone()]), 297 | Some(1), 298 | ), 299 | BuildError::composition_error( 300 | Some("BOO".to_string()), 301 | Some("boo".to_string()), 302 | Some(vec![error_node.clone()]), 303 | Some(2), 304 | ), 305 | ] 306 | .into(); 307 | 308 | let actual_value: Value = serde_json::from_str( 309 | &serde_json::to_string(&build_errors) 310 | .expect("Could not convert build errors to string"), 311 | ) 312 | .expect("Could not convert build error string to serde_json::Value"); 313 | 314 | let expected_value = json!({ 315 | "build_errors": [ 316 | { 317 | "message": "wow", 318 | "code": null, 319 | "type": "composition", 320 | "nodes": [ 321 | { 322 | "subgraph": "foo", 323 | "source": null, 324 | "start": null, 325 | "end": null 326 | } 327 | ], 328 | "omittedNodesCount": 1 329 | }, 330 | { 331 | "message": "boo", 332 | "code": "BOO", 333 | "type": "composition", 334 | "nodes": [ 335 | { 336 | "subgraph": "foo", 337 | "source": null, 338 | "start": null, 339 | "end": null 340 | } 341 | ], 342 | "omittedNodesCount": 2 343 | } 344 | ] 345 | }); 346 | assert_eq!(actual_value, expected_value); 347 | } 348 | 349 | #[test] 350 | fn it_can_deserialize() { 351 | let msg = "wow".to_string(); 352 | let code = "boo".to_string(); 353 | let actual_struct = serde_json::from_str( 354 | &json!({ "message": &msg, "code": &code, "type": "composition", "nodes": null, "omittedNodesCount": 12 }).to_string(), 355 | ).unwrap(); 356 | let expected_struct = 357 | BuildError::composition_error(Some(code.clone()), Some(msg.clone()), None, Some(12)); 358 | assert_eq!(expected_struct, actual_struct); 359 | } 360 | } 361 | -------------------------------------------------------------------------------- /apollo-federation-types/src/config/version.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "json_schema")] 2 | use schemars::{json_schema, Schema, SchemaGenerator}; 3 | use semver::Version; 4 | use serde::de::Error; 5 | use serde::{Deserialize, Deserializer}; 6 | use serde_with::{DeserializeFromStr, SerializeDisplay}; 7 | #[cfg(feature = "json_schema")] 8 | use std::borrow::Cow; 9 | use std::{ 10 | fmt::{self, Display}, 11 | str::FromStr, 12 | }; 13 | 14 | use crate::config::ConfigError; 15 | 16 | pub trait PluginVersion { 17 | fn get_major_version(&self) -> u64; 18 | fn get_tarball_version(&self) -> String; 19 | } 20 | 21 | #[derive(Debug, Clone, SerializeDisplay, DeserializeFromStr, PartialEq, Eq)] 22 | pub enum RouterVersion { 23 | Exact(Version), 24 | LatestOne, 25 | LatestTwo, 26 | } 27 | 28 | impl PluginVersion for RouterVersion { 29 | fn get_major_version(&self) -> u64 { 30 | match self { 31 | Self::LatestOne => 1, 32 | Self::LatestTwo => 2, 33 | Self::Exact(v) => v.major, 34 | } 35 | } 36 | 37 | fn get_tarball_version(&self) -> String { 38 | match self { 39 | Self::Exact(v) => format!("v{v}"), 40 | // the endpoint for getting router plugins via rover.apollo.dev 41 | // uses "latest-plugin" instead of "latest" zsto get the latest version 42 | Self::LatestOne => "latest-plugin".to_string(), 43 | Self::LatestTwo => "latest-2".to_string(), 44 | } 45 | } 46 | } 47 | 48 | impl Display for RouterVersion { 49 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 50 | let result = match self { 51 | Self::LatestOne => "1".to_string(), 52 | Self::LatestTwo => "2".to_string(), 53 | Self::Exact(version) => format!("={version}"), 54 | }; 55 | write!(f, "{result}") 56 | } 57 | } 58 | 59 | impl FromStr for RouterVersion { 60 | type Err = ConfigError; 61 | 62 | fn from_str(input: &str) -> std::result::Result { 63 | let invalid_version = ConfigError::InvalidConfiguration { 64 | message: format!("Specified version `{input}` is not supported. You can specify '1', '2', 'latest', or a fully qualified version prefixed with an '=', like: =1.0.0"), 65 | }; 66 | if input.len() > 1 && (input.starts_with('=') || input.starts_with('v')) { 67 | if let Ok(version) = input[1..].parse::() { 68 | if version.major == 1 { 69 | Ok(Self::Exact(version)) 70 | } else { 71 | Err(invalid_version) 72 | } 73 | } else { 74 | Err(invalid_version) 75 | } 76 | } else { 77 | match input { 78 | "1" => Ok(Self::LatestOne), 79 | "2" | "latest" => Ok(Self::LatestTwo), 80 | _ => Err(invalid_version), 81 | } 82 | } 83 | } 84 | } 85 | 86 | #[derive(Debug, Clone, SerializeDisplay, Eq, PartialEq, Default)] 87 | pub enum FederationVersion { 88 | #[default] 89 | LatestFedOne, 90 | LatestFedTwo, 91 | ExactFedOne(Version), 92 | ExactFedTwo(Version), 93 | } 94 | 95 | impl FederationVersion { 96 | pub fn get_exact(&self) -> Option<&Version> { 97 | match self { 98 | Self::ExactFedOne(version) | Self::ExactFedTwo(version) => Some(version), 99 | _ => None, 100 | } 101 | } 102 | 103 | fn is_latest(&self) -> bool { 104 | matches!(self, Self::LatestFedOne) || matches!(self, Self::LatestFedTwo) 105 | } 106 | 107 | pub fn is_fed_one(&self) -> bool { 108 | matches!(self, Self::LatestFedOne) || matches!(self, Self::ExactFedOne(_)) 109 | } 110 | 111 | pub fn is_fed_two(&self) -> bool { 112 | matches!(self, Self::LatestFedTwo) || matches!(self, Self::ExactFedTwo(_)) 113 | } 114 | 115 | pub fn supports_arm_linux(&self) -> bool { 116 | let mut supports_arm = false; 117 | if self.is_latest() { 118 | supports_arm = true; 119 | } else if let Some(exact) = self.get_exact() { 120 | if self.is_fed_one() { 121 | // 0.37.0 is the first fed2 version that supports ARM 122 | supports_arm = exact.minor >= 37; 123 | } else if self.is_fed_two() { 124 | // 2.1.0 is the first fed2 version that supports ARM 125 | supports_arm = exact.minor >= 1; 126 | } 127 | } 128 | supports_arm 129 | } 130 | 131 | pub fn supports_arm_macos(&self) -> bool { 132 | let mut supports_arm = false; 133 | // No published fed1 version supports aarch64 on macOS 134 | if self.is_fed_two() { 135 | if self.is_latest() { 136 | supports_arm = true; 137 | } else if let Some(exact) = self.get_exact() { 138 | // v2.7.3 is the earliest version published with aarch64 support for macOS 139 | supports_arm = exact.ge(&Version::parse("2.7.3").unwrap()) 140 | } 141 | } 142 | supports_arm 143 | } 144 | } 145 | 146 | impl PluginVersion for FederationVersion { 147 | fn get_major_version(&self) -> u64 { 148 | match self { 149 | Self::LatestFedOne | Self::ExactFedOne(_) => 0, 150 | Self::LatestFedTwo | Self::ExactFedTwo(_) => 2, 151 | } 152 | } 153 | 154 | fn get_tarball_version(&self) -> String { 155 | match self { 156 | Self::LatestFedOne => "latest-0".to_string(), 157 | Self::LatestFedTwo => "latest-2".to_string(), 158 | Self::ExactFedOne(v) | Self::ExactFedTwo(v) => format!("v{v}"), 159 | } 160 | } 161 | } 162 | 163 | impl Display for FederationVersion { 164 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 165 | let result = match self { 166 | Self::LatestFedOne => "0".to_string(), 167 | Self::LatestFedTwo => "2".to_string(), 168 | Self::ExactFedOne(version) | Self::ExactFedTwo(version) => format!("={version}"), 169 | }; 170 | write!(f, "{result}") 171 | } 172 | } 173 | 174 | impl FromStr for FederationVersion { 175 | type Err = ConfigError; 176 | 177 | fn from_str(input: &str) -> std::result::Result { 178 | let invalid_version = ConfigError::InvalidConfiguration { 179 | message: format!("Specified version `{input}` is not supported. You can either specify '1', '2', or a fully qualified version prefixed with an '=', like: =2.0.0"), 180 | }; 181 | if input.len() > 1 && (input.starts_with('=') || input.starts_with('v')) { 182 | if let Ok(version) = input[1..].parse::() { 183 | if version.major == 0 { 184 | if version.minor >= 36 { 185 | Ok(Self::ExactFedOne(version)) 186 | } else { 187 | Err(ConfigError::InvalidConfiguration { message: format!("Specified version `{input}` is not supported. The earliest version you can specify for federation 1 is '=0.36.0'") }) 188 | } 189 | } else if version.major == 2 { 190 | if version >= "2.0.0-preview.9".parse::().unwrap() { 191 | Ok(Self::ExactFedTwo(version)) 192 | } else { 193 | Err(ConfigError::InvalidConfiguration { message: format!("Specified version `{input}` is not supported. The earliest version you can specify for federation 2 is '=2.0.0-preview.9'") }) 194 | } 195 | } else { 196 | Err(invalid_version) 197 | } 198 | } else { 199 | Err(invalid_version) 200 | } 201 | } else { 202 | match input { 203 | "0" | "1" | "latest-0" | "latest-1" => Ok(Self::LatestFedOne), 204 | "2" | "latest-2" => Ok(Self::LatestFedTwo), 205 | _ => Err(invalid_version), 206 | } 207 | } 208 | } 209 | } 210 | 211 | #[cfg(feature = "json_schema")] 212 | impl schemars::JsonSchema for FederationVersion { 213 | fn schema_name() -> Cow<'static, str> { 214 | Cow::Borrowed("FederationVersion") 215 | } 216 | 217 | fn json_schema(_gen: &mut SchemaGenerator) -> Schema { 218 | json_schema!({ 219 | "pattern": r#"^(1|2|=2\.\d+\.\d+.*)$"# 220 | }) 221 | } 222 | } 223 | 224 | impl<'de> Deserialize<'de> for FederationVersion { 225 | fn deserialize(deserializer: D) -> Result 226 | where 227 | D: Deserializer<'de>, 228 | { 229 | struct Visitor; 230 | 231 | impl serde::de::Visitor<'_> for Visitor { 232 | type Value = FederationVersion; 233 | 234 | fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { 235 | f.write_str("literal '1' or '2' (as a string or number), or a fully qualified version prefixed with an '=', like: =2.0.0") 236 | } 237 | 238 | fn visit_u64(self, num: u64) -> Result 239 | where 240 | E: Error, 241 | { 242 | match num { 243 | 0 | 1 => Ok(FederationVersion::LatestFedOne), 244 | 2 => Ok(FederationVersion::LatestFedTwo), 245 | _ => Err(Error::custom(format!( 246 | "specified version `{num}` is not supported" 247 | ))), 248 | } 249 | } 250 | 251 | fn visit_str(self, id: &str) -> Result 252 | where 253 | E: Error, 254 | { 255 | FederationVersion::from_str(id).map_err(|e| Error::custom(e.to_string())) 256 | } 257 | } 258 | deserializer.deserialize_any(Visitor) 259 | } 260 | } 261 | 262 | #[cfg(test)] 263 | mod test_federation_version { 264 | use rstest::rstest; 265 | use serde_yaml::Value; 266 | 267 | use crate::config::FederationVersion; 268 | 269 | #[test] 270 | fn test_deserialization() { 271 | assert_eq!( 272 | FederationVersion::LatestFedTwo, 273 | serde_yaml::from_value(Value::String(String::from("2"))).unwrap() 274 | ); 275 | assert_eq!( 276 | FederationVersion::LatestFedTwo, 277 | serde_yaml::from_value(Value::Number(2.into())).unwrap() 278 | ); 279 | assert_eq!( 280 | FederationVersion::LatestFedTwo, 281 | serde_yaml::from_str("latest-2").unwrap() 282 | ); 283 | 284 | assert_eq!( 285 | FederationVersion::LatestFedOne, 286 | serde_yaml::from_str("1").unwrap() 287 | ); 288 | assert_eq!( 289 | FederationVersion::LatestFedOne, 290 | serde_yaml::from_str("\"1\"").unwrap() 291 | ); 292 | assert_eq!( 293 | FederationVersion::LatestFedOne, 294 | serde_yaml::from_str("latest-1").unwrap() 295 | ); 296 | assert_eq!( 297 | FederationVersion::LatestFedOne, 298 | serde_yaml::from_str("latest-0").unwrap() 299 | ); 300 | 301 | assert_eq!( 302 | FederationVersion::ExactFedTwo("2.3.4".parse().unwrap()), 303 | serde_yaml::from_str("=2.3.4").unwrap() 304 | ); 305 | assert_eq!( 306 | FederationVersion::ExactFedTwo("2.3.4".parse().unwrap()), 307 | serde_yaml::from_str("v2.3.4").unwrap() 308 | ); 309 | 310 | assert_eq!( 311 | FederationVersion::ExactFedOne("0.37.8".parse().unwrap()), 312 | serde_yaml::from_str("=0.37.8").unwrap() 313 | ); 314 | assert_eq!( 315 | FederationVersion::ExactFedOne("0.37.8".parse().unwrap()), 316 | serde_yaml::from_str("v0.37.8").unwrap() 317 | ); 318 | } 319 | 320 | #[rstest] 321 | #[case::fed1_latest(FederationVersion::LatestFedOne, true)] 322 | #[case::fed1_supported(FederationVersion::ExactFedOne("0.37.2".parse().unwrap()), true)] 323 | #[case::fed1_supported_boundary(FederationVersion::ExactFedOne("0.37.1".parse().unwrap()), true)] 324 | #[case::fed1_unsupported(FederationVersion::ExactFedOne("0.25.0".parse().unwrap()), false)] 325 | #[case::fed2_latest(FederationVersion::LatestFedTwo, true)] 326 | #[case::fed2_supported(FederationVersion::ExactFedTwo("2.4.5".parse().unwrap()), true)] 327 | #[case::fed2_supported_boundary(FederationVersion::ExactFedTwo("2.1.0".parse().unwrap()), true)] 328 | #[case::fed2_unsupported(FederationVersion::ExactFedTwo("2.0.1".parse().unwrap()), false)] 329 | fn test_supports_arm_linux(#[case] version: FederationVersion, #[case] expected: bool) { 330 | assert_eq!(version.supports_arm_linux(), expected) 331 | } 332 | 333 | #[rstest] 334 | #[case::fed1_latest(FederationVersion::LatestFedOne, false)] 335 | #[case::fed1_unsupported(FederationVersion::ExactFedOne("0.37.2".parse().unwrap()), false)] 336 | #[case::fed2_latest(FederationVersion::LatestFedTwo, true)] 337 | #[case::fed2_supported(FederationVersion::ExactFedTwo("2.8.1".parse().unwrap()), true)] 338 | #[case::fed2_supported_boundary(FederationVersion::ExactFedTwo("2.7.3".parse().unwrap()), true)] 339 | #[case::fed2_unsupported(FederationVersion::ExactFedTwo("2.6.5".parse().unwrap()), false)] 340 | fn test_supports_arm_macos(#[case] version: FederationVersion, #[case] expected: bool) { 341 | assert_eq!(version.supports_arm_macos(), expected) 342 | } 343 | } 344 | 345 | #[cfg(feature = "json_schema")] 346 | #[cfg(test)] 347 | mod json_schema_tests { 348 | use super::*; 349 | use schemars::{schema_for, JsonSchema, SchemaGenerator}; 350 | 351 | #[test] 352 | fn test_schema_name() { 353 | assert_eq!(FederationVersion::schema_name(), "FederationVersion"); 354 | } 355 | 356 | #[test] 357 | fn test_json_schema() { 358 | let mut gen = SchemaGenerator::default(); 359 | let schema = FederationVersion::json_schema(&mut gen); 360 | 361 | let value = serde_json::to_value(&schema).unwrap(); 362 | assert_eq!(value["pattern"], r#"^(1|2|=2\.\d+\.\d+.*)$"#); 363 | // The schema should not have a type field since it's only setting pattern 364 | assert!(value["type"].is_null()); 365 | } 366 | 367 | #[test] 368 | fn test_serialize_to_value() { 369 | let schema = schema_for!(FederationVersion); 370 | let serialized = serde_json::to_value(&schema).unwrap(); 371 | 372 | assert!(serialized.is_object()); 373 | assert_eq!( 374 | serialized["$schema"], 375 | "https://json-schema.org/draft/2020-12/schema" 376 | ); 377 | assert_eq!(serialized["title"], "FederationVersion"); 378 | assert_eq!(serialized["pattern"], r#"^(1|2|=2\.\d+\.\d+.*)$"#); 379 | } 380 | 381 | #[test] 382 | fn test_serialize_to_json() { 383 | let schema = schema_for!(FederationVersion); 384 | let serialized = serde_json::to_string_pretty(&schema).unwrap(); 385 | 386 | assert!( 387 | serialized.contains("\"$schema\": \"https://json-schema.org/draft/2020-12/schema\"") 388 | ); 389 | assert!(serialized.contains("\"title\": \"FederationVersion\"")); 390 | assert!(serialized.contains("\"pattern\": \"^(1|2|=2\\\\.\\\\d+\\\\.\\\\d+.*)$\"")); 391 | } 392 | } 393 | -------------------------------------------------------------------------------- /apollo-federation-types/src/config/supergraph.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::BTreeMap, fs, path::PathBuf}; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | use crate::{ 6 | config::{ConfigError, ConfigResult, FederationVersion, SubgraphConfig}, 7 | javascript::SubgraphDefinition, 8 | }; 9 | 10 | /// The configuration for a single supergraph 11 | /// composed of multiple subgraphs. 12 | #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] 13 | #[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))] 14 | pub struct SupergraphConfig { 15 | // Store config in a BTreeMap, as HashMap is non-deterministic. 16 | subgraphs: BTreeMap, 17 | 18 | // The version requirement for the supergraph binary. 19 | federation_version: Option, 20 | } 21 | 22 | impl SupergraphConfig { 23 | /// Creates a new SupergraphConfig 24 | pub fn new( 25 | subgraphs: BTreeMap, 26 | federation_version: Option, 27 | ) -> SupergraphConfig { 28 | SupergraphConfig { 29 | subgraphs, 30 | federation_version, 31 | } 32 | } 33 | /// Create a new SupergraphConfig from a YAML string in memory. 34 | pub fn new_from_yaml(yaml: &str) -> ConfigResult { 35 | let parsed_config: SupergraphConfig = 36 | serde_yaml::from_str(yaml).map_err(|e| ConfigError::InvalidConfiguration { 37 | message: e.to_string(), 38 | })?; 39 | 40 | log::debug!("{parsed_config:?}"); 41 | 42 | Ok(parsed_config) 43 | } 44 | 45 | /// Create a new SupergraphConfig from a JSON string in memory. 46 | pub fn new_from_json(json: &str) -> ConfigResult { 47 | let parsed_config: SupergraphConfig = 48 | serde_json::from_str(json).map_err(|e| ConfigError::InvalidConfiguration { 49 | message: e.to_string(), 50 | })?; 51 | 52 | log::debug!("{parsed_config:?}"); 53 | 54 | Ok(parsed_config) 55 | } 56 | 57 | /// Create a new SupergraphConfig from a YAML file. 58 | pub fn new_from_yaml_file>(config_path: P) -> ConfigResult { 59 | let config_path: PathBuf = config_path.into(); 60 | let supergraph_yaml = 61 | fs::read_to_string(&config_path).map_err(|e| ConfigError::MissingFile { 62 | file_path: config_path.display().to_string(), 63 | message: e.to_string(), 64 | })?; 65 | 66 | let parsed_config = SupergraphConfig::new_from_yaml(&supergraph_yaml)?; 67 | 68 | Ok(parsed_config) 69 | } 70 | 71 | /// Returns a Vec of resolved subgraphs, if and only if they are all resolved. 72 | /// Resolved in this sense means that each subgraph config includes 73 | /// a name, a URL, and raw SDL. 74 | pub fn get_subgraph_definitions(&self) -> ConfigResult> { 75 | let mut subgraph_definitions = Vec::new(); 76 | let mut unresolved_subgraphs = Vec::new(); 77 | for (subgraph_name, subgraph_config) in &self.subgraphs { 78 | if let Some(sdl) = subgraph_config.get_sdl() { 79 | if let Some(routing_url) = &subgraph_config.routing_url { 80 | subgraph_definitions.push(SubgraphDefinition { 81 | name: subgraph_name.clone(), 82 | url: routing_url.clone(), 83 | sdl, 84 | }); 85 | } else { 86 | unresolved_subgraphs.push(subgraph_name); 87 | } 88 | } else { 89 | unresolved_subgraphs.push(subgraph_name); 90 | } 91 | } 92 | if !unresolved_subgraphs.is_empty() { 93 | Err(ConfigError::SubgraphsNotResolved { 94 | subgraph_names: format!("{:?}", &unresolved_subgraphs), 95 | }) 96 | } else if subgraph_definitions.is_empty() { 97 | Err(ConfigError::NoSubgraphsFound) 98 | } else { 99 | Ok(subgraph_definitions) 100 | } 101 | } 102 | 103 | /// Updates the federation_version for a configuration 104 | pub fn set_federation_version(&mut self, federation_version: FederationVersion) { 105 | self.federation_version = Some(federation_version); 106 | } 107 | 108 | /// Gets the current federation_version for a configuration 109 | pub fn get_federation_version(&self) -> Option { 110 | self.federation_version.clone() 111 | } 112 | 113 | /// Merges the subgraphs of another [`SupergraphConfig`] into this one; the 114 | /// other config takes precedence when there are overlaps 115 | pub fn merge_subgraphs(&mut self, other: &SupergraphConfig) { 116 | for (key, other_subgraph) in other.subgraphs.iter() { 117 | let other_subgraph = other_subgraph.clone(); 118 | // SubgraphConfig always has a schema. For routing_url, we take 119 | // `other` if they both exist (ie, we let local configuration 120 | // override) 121 | let merged_subgraph = match self.subgraphs.get(key) { 122 | Some(my_subgraph) => SubgraphConfig { 123 | routing_url: other_subgraph 124 | .routing_url 125 | .or(my_subgraph.routing_url.clone()), 126 | schema: other_subgraph.schema, 127 | }, 128 | None => other_subgraph, 129 | }; 130 | self.subgraphs.insert(key.to_string(), merged_subgraph); 131 | } 132 | } 133 | } 134 | 135 | impl From> for SupergraphConfig { 136 | fn from(input: Vec) -> Self { 137 | let mut subgraphs = BTreeMap::new(); 138 | for subgraph_definition in input { 139 | subgraphs.insert( 140 | subgraph_definition.name, 141 | SubgraphConfig { 142 | routing_url: Some(subgraph_definition.url), 143 | schema: crate::config::SchemaSource::Sdl { 144 | sdl: subgraph_definition.sdl, 145 | }, 146 | }, 147 | ); 148 | } 149 | Self { 150 | subgraphs, 151 | federation_version: None, 152 | } 153 | } 154 | } 155 | 156 | // implement IntoIterator so you can do: 157 | // for (subgraph_name, subgraph_metadata) in supergraph_config.into_iter() { ... } 158 | impl IntoIterator for SupergraphConfig { 159 | type Item = (String, SubgraphConfig); 160 | type IntoIter = std::collections::btree_map::IntoIter; 161 | 162 | fn into_iter(self) -> Self::IntoIter { 163 | self.subgraphs.into_iter() 164 | } 165 | } 166 | 167 | impl FromIterator<(String, SubgraphConfig)> for SupergraphConfig { 168 | fn from_iter>(iter: T) -> Self { 169 | Self { 170 | subgraphs: iter.into_iter().collect::>(), 171 | federation_version: None, 172 | } 173 | } 174 | } 175 | 176 | #[cfg(test)] 177 | mod tests { 178 | use std::{collections::BTreeMap, convert::TryFrom, fs, path::PathBuf}; 179 | 180 | use assert_fs::TempDir; 181 | use semver::Version; 182 | 183 | use super::SupergraphConfig; 184 | use crate::config::{FederationVersion, SchemaSource, SubgraphConfig}; 185 | 186 | #[test] 187 | fn it_can_parse_valid_config_without_version() { 188 | let raw_good_yaml = r#"--- 189 | subgraphs: 190 | films: 191 | routing_url: https://films.example.com 192 | schema: 193 | file: ./good-films.graphql 194 | people: 195 | routing_url: https://people.example.com 196 | schema: 197 | file: ./good-people.graphql 198 | "#; 199 | 200 | let config = SupergraphConfig::new_from_yaml(raw_good_yaml); 201 | assert!(config.is_ok()); 202 | let config = config.unwrap(); 203 | assert_eq!(config.federation_version, None); 204 | } 205 | 206 | #[test] 207 | fn it_can_parse_valid_config_without_version_json() { 208 | let raw_good_json = r#" 209 | { 210 | "subgraphs": { 211 | "films": { 212 | "routing_url": "https://films.example.com", 213 | "schema": { 214 | "file": "./good-films.graphql" 215 | } 216 | }, 217 | "people": { 218 | "routing_url": "https://people.example.com", 219 | "schema": { 220 | "file": "./good-people.graphql" 221 | } 222 | } 223 | } 224 | } 225 | "#; 226 | 227 | let config = SupergraphConfig::new_from_json(raw_good_json); 228 | println!("{:?}", config); 229 | assert!(config.is_ok()); 230 | let config = config.unwrap(); 231 | assert_eq!(config.federation_version, None); 232 | } 233 | 234 | #[test] 235 | fn it_can_parse_valid_config_fed_zero() { 236 | let raw_good_yaml = r#"--- 237 | federation_version: 0 238 | subgraphs: 239 | films: 240 | routing_url: https://films.example.com 241 | schema: 242 | file: ./good-films.graphql 243 | people: 244 | routing_url: https://people.example.com 245 | schema: 246 | file: ./good-people.graphql 247 | "#; 248 | 249 | let config = SupergraphConfig::new_from_yaml(raw_good_yaml).unwrap(); 250 | assert_eq!( 251 | config.federation_version, 252 | Some(FederationVersion::LatestFedOne) 253 | ); 254 | } 255 | 256 | #[test] 257 | fn it_can_parse_valid_config_fed_zero_json() { 258 | let raw_json_yaml = r#" 259 | { 260 | "federation_version": 0, 261 | "subgraphs": { 262 | "films": { 263 | "routing_url": "https://films.example.com", 264 | "schema": { 265 | "file": "./good-films.graphql" 266 | } 267 | }, 268 | "people": { 269 | "routing_url": "https://people.example.com", 270 | "schema": { 271 | "file": "./good-people.graphql" 272 | } 273 | } 274 | } 275 | } 276 | "#; 277 | 278 | let config = SupergraphConfig::new_from_json(raw_json_yaml).unwrap(); 279 | assert_eq!( 280 | config.federation_version, 281 | Some(FederationVersion::LatestFedOne) 282 | ); 283 | } 284 | 285 | #[test] 286 | fn it_can_parse_valid_config_fed_one() { 287 | let raw_good_yaml = r#"--- 288 | federation_version: 1 289 | subgraphs: 290 | films: 291 | routing_url: https://films.example.com 292 | schema: 293 | file: ./good-films.graphql 294 | people: 295 | routing_url: https://people.example.com 296 | schema: 297 | file: ./good-people.graphql 298 | "#; 299 | 300 | let config = SupergraphConfig::new_from_yaml(raw_good_yaml).unwrap(); 301 | assert_eq!( 302 | config.federation_version, 303 | Some(FederationVersion::LatestFedOne) 304 | ); 305 | } 306 | 307 | #[test] 308 | fn it_can_parse_valid_config_fed_one_json() { 309 | let raw_good_json = r#" 310 | { 311 | "federation_version": 1, 312 | "subgraphs": { 313 | "films": { 314 | "routing_url": "https://films.example.com", 315 | "schema": { 316 | "file": "./good-films.graphql" 317 | } 318 | }, 319 | "people": { 320 | "routing_url": "https://people.example.com", 321 | "schema": { 322 | "file": "./good-people.graphql" 323 | } 324 | } 325 | } 326 | } 327 | "#; 328 | 329 | let config = SupergraphConfig::new_from_json(raw_good_json).unwrap(); 330 | assert_eq!( 331 | config.federation_version, 332 | Some(FederationVersion::LatestFedOne) 333 | ); 334 | } 335 | 336 | #[test] 337 | fn it_can_parse_valid_config_fed_two() { 338 | let raw_good_yaml = r#"--- 339 | federation_version: 2 340 | subgraphs: 341 | films: 342 | routing_url: https://films.example.com 343 | schema: 344 | file: ./good-films.graphql 345 | people: 346 | routing_url: https://people.example.com 347 | schema: 348 | file: ./good-people.graphql 349 | "#; 350 | 351 | let config = SupergraphConfig::new_from_yaml(raw_good_yaml).unwrap(); 352 | assert_eq!( 353 | config.federation_version, 354 | Some(FederationVersion::LatestFedTwo) 355 | ); 356 | } 357 | 358 | #[test] 359 | fn it_can_parse_valid_config_fed_two_json() { 360 | let raw_good_json = r#" 361 | { 362 | "federation_version": 2, 363 | "subgraphs": { 364 | "films": { 365 | "routing_url": "https://films.example.com", 366 | "schema": { 367 | "file": "./good-films.graphql" 368 | } 369 | }, 370 | "people": { 371 | "routing_url": "https://people.example.com", 372 | "schema": { 373 | "file": "./good-people.graphql" 374 | } 375 | } 376 | } 377 | } 378 | "#; 379 | 380 | let config = SupergraphConfig::new_from_json(raw_good_json).unwrap(); 381 | assert_eq!( 382 | config.federation_version, 383 | Some(FederationVersion::LatestFedTwo) 384 | ); 385 | } 386 | 387 | #[test] 388 | fn it_can_parse_valid_config_fed_one_exact() { 389 | let raw_good_yaml = r#"--- 390 | federation_version: =0.36.0 391 | subgraphs: 392 | films: 393 | routing_url: https://films.example.com 394 | schema: 395 | file: ./good-films.graphql 396 | people: 397 | routing_url: https://people.example.com 398 | schema: 399 | file: ./good-people.graphql 400 | "#; 401 | 402 | let config = SupergraphConfig::new_from_yaml(raw_good_yaml).unwrap(); 403 | assert_eq!( 404 | config.federation_version, 405 | Some(FederationVersion::ExactFedOne( 406 | Version::parse("0.36.0").unwrap() 407 | )) 408 | ); 409 | } 410 | 411 | #[test] 412 | fn it_can_parse_valid_config_fed_one_exact_json() { 413 | let raw_good_json = r#" 414 | { 415 | "federation_version": "=0.36.0", 416 | "subgraphs": { 417 | "films": { 418 | "routing_url": "https://films.example.com", 419 | "schema": { 420 | "file": "./good-films.graphql" 421 | } 422 | }, 423 | "people": { 424 | "routing_url": "https://people.example.com", 425 | "schema": { 426 | "file": "./good-people.graphql" 427 | } 428 | } 429 | } 430 | } 431 | "#; 432 | 433 | let config = SupergraphConfig::new_from_json(raw_good_json).unwrap(); 434 | assert_eq!( 435 | config.federation_version, 436 | Some(FederationVersion::ExactFedOne( 437 | Version::parse("0.36.0").unwrap() 438 | )) 439 | ); 440 | } 441 | 442 | #[test] 443 | fn it_can_parse_valid_config_fed_two_exact() { 444 | let raw_good_yaml = r#"--- 445 | federation_version: =2.0.0 446 | subgraphs: 447 | films: 448 | routing_url: https://films.example.com 449 | schema: 450 | file: ./good-films.graphql 451 | people: 452 | routing_url: https://people.example.com 453 | schema: 454 | file: ./good-people.graphql 455 | "#; 456 | 457 | let config = SupergraphConfig::new_from_yaml(raw_good_yaml).unwrap(); 458 | assert_eq!( 459 | config.federation_version, 460 | Some(FederationVersion::ExactFedTwo( 461 | Version::parse("2.0.0").unwrap() 462 | )) 463 | ); 464 | } 465 | 466 | #[test] 467 | fn it_can_parse_valid_config_fed_two_exact_json() { 468 | let raw_good_json = r#" 469 | { 470 | "federation_version": "=2.0.0", 471 | "subgraphs": { 472 | "films": { 473 | "routing_url": "https://films.example.com", 474 | "schema": { 475 | "file": "./good-films.graphql" 476 | } 477 | }, 478 | "people": { 479 | "routing_url": "https://people.example.com", 480 | "schema": { 481 | "file": "./good-people.graphql" 482 | } 483 | } 484 | } 485 | } 486 | "#; 487 | 488 | let config = SupergraphConfig::new_from_json(raw_good_json).unwrap(); 489 | assert_eq!( 490 | config.federation_version, 491 | Some(FederationVersion::ExactFedTwo( 492 | Version::parse("2.0.0").unwrap() 493 | )) 494 | ); 495 | } 496 | 497 | #[test] 498 | fn it_can_parse_valid_config_from_fs() { 499 | let raw_good_yaml = r#"--- 500 | subgraphs: 501 | films: 502 | routing_url: https://films.example.com 503 | schema: 504 | file: ./good-films.graphql 505 | people: 506 | routing_url: https://people.example.com 507 | schema: 508 | file: ./good-people.graphql 509 | "#; 510 | 511 | let tmp_home = TempDir::new().unwrap(); 512 | let mut config_path = PathBuf::try_from(tmp_home.path().to_path_buf()).unwrap(); 513 | config_path.push("config.yaml"); 514 | fs::write(&config_path, raw_good_yaml).unwrap(); 515 | 516 | assert!(SupergraphConfig::new_from_yaml_file(&config_path).is_ok()); 517 | } 518 | 519 | #[test] 520 | fn it_can_parse_valid_config_with_introspection() { 521 | let raw_good_yaml = r#"--- 522 | subgraphs: 523 | films: 524 | routing_url: https://films.example.com 525 | schema: 526 | file: ./films.graphql 527 | people: 528 | schema: 529 | subgraph_url: https://people.example.com 530 | reviews: 531 | schema: 532 | graphref: mygraph@current 533 | subgraph: reviews 534 | "#; 535 | 536 | assert!(SupergraphConfig::new_from_yaml(raw_good_yaml).is_ok()); 537 | } 538 | 539 | #[test] 540 | fn it_can_parse_valid_config_with_introspection_json() { 541 | let raw_good_json = r#" 542 | { 543 | "subgraphs": { 544 | "films": { 545 | "routing_url": "https://films.example.com", 546 | "schema": { 547 | "file": "./films.graphql" 548 | } 549 | }, 550 | "people": { 551 | "schema": { 552 | "subgraph_url": "https://people.example.com" 553 | } 554 | }, 555 | "reviews": { 556 | "schema": { 557 | "graphref": "mygraph@current", 558 | "subgraph": "reviews" 559 | } 560 | } 561 | } 562 | } 563 | "#; 564 | 565 | assert!(SupergraphConfig::new_from_json(raw_good_json).is_ok()); 566 | } 567 | 568 | #[test] 569 | fn it_errors_on_invalid_config() { 570 | let raw_bad_yaml = r#"--- 571 | subgraphs: 572 | films: 573 | routing_______url: https://films.example.com 574 | schemaaaa: 575 | file:: ./good-films.graphql 576 | people: 577 | routing____url: https://people.example.com 578 | schema_____file: ./good-people.graphql"#; 579 | 580 | assert!(SupergraphConfig::new_from_yaml(raw_bad_yaml).is_err()) 581 | } 582 | 583 | #[test] 584 | fn it_errors_on_invalid_config_json() { 585 | let raw_bad_yaml = r#" 586 | subgraphs: 587 | films: 588 | routing_______url: https://films.example.com 589 | schemaaaa: 590 | file:: ./good-films.graphql 591 | people: 592 | routing____url: https://people.example.com 593 | schema_____file: ./good-people.graphql"#; 594 | 595 | assert!(SupergraphConfig::new_from_yaml(raw_bad_yaml).is_err()) 596 | } 597 | 598 | #[test] 599 | fn it_errs_on_bad_version() { 600 | let raw_good_yaml = r#"--- 601 | federation_version: 3" 602 | subgraphs: 603 | films: 604 | routing_url: https://films.example.com 605 | schema: 606 | file: ./good-films.graphql 607 | people: 608 | routing_url: https://people.example.com 609 | schema: 610 | file: ./good-people.graphql 611 | "#; 612 | 613 | assert!(SupergraphConfig::new_from_yaml(raw_good_yaml).is_err()) 614 | } 615 | 616 | #[test] 617 | fn it_errs_on_bad_version_json() { 618 | let raw_good_yaml = r#" 619 | { 620 | "federation_version": "3", 621 | "subgraphs": { 622 | "films": { 623 | "routing_url": "https://films.example.com", 624 | "schema": { 625 | "file": "./good-films.graphql" 626 | } 627 | }, 628 | "people": { 629 | "routing_url": "https://people.example.com", 630 | "schema": { 631 | "file": "./good-people.graphql" 632 | } 633 | } 634 | } 635 | } 636 | "#; 637 | 638 | assert!(SupergraphConfig::new_from_yaml(raw_good_yaml).is_err()) 639 | } 640 | 641 | #[test] 642 | fn test_merge_subgraphs() { 643 | let raw_base_config = r#"--- 644 | federation_version: 2 645 | subgraphs: 646 | films: 647 | routing_url: https://films.example.com 648 | schema: 649 | file: ./good-films.graphql 650 | people: 651 | routing_url: https://people.example.com 652 | schema: 653 | file: ./good-people.graphql 654 | robots: 655 | routing_url: https://robots.example.com 656 | schema: 657 | file: ./good-robots.graphql 658 | "#; 659 | let raw_override_config = r#"--- 660 | federation_version: 1 661 | subgraphs: 662 | films: 663 | routing_url: https://films.example.com/graphql 664 | schema: 665 | file: ./good-films.graphql 666 | books: 667 | routing_url: https://books.example.com 668 | schema: 669 | file: ./good-books.graphql 670 | robots: 671 | schema: 672 | file: ./better-robots.graphql 673 | "#; 674 | let mut base_config = SupergraphConfig::new_from_yaml(raw_base_config) 675 | .expect("Failed to parse supergraph config"); 676 | 677 | let override_config = SupergraphConfig::new_from_yaml(raw_override_config) 678 | .expect("Failed to parse supergraph config"); 679 | 680 | base_config.merge_subgraphs(&override_config); 681 | 682 | assert_eq!( 683 | base_config.get_federation_version(), 684 | Some(FederationVersion::LatestFedTwo) 685 | ); 686 | 687 | let expected_subgraphs = BTreeMap::from([ 688 | ( 689 | "films".to_string(), 690 | SubgraphConfig { 691 | routing_url: Some("https://films.example.com/graphql".to_string()), 692 | schema: SchemaSource::File { 693 | file: "./good-films.graphql".into(), 694 | }, 695 | }, 696 | ), 697 | ( 698 | "books".to_string(), 699 | SubgraphConfig { 700 | routing_url: Some("https://books.example.com".to_string()), 701 | schema: SchemaSource::File { 702 | file: "./good-books.graphql".into(), 703 | }, 704 | }, 705 | ), 706 | ( 707 | "people".to_string(), 708 | SubgraphConfig { 709 | routing_url: Some("https://people.example.com".to_string()), 710 | schema: SchemaSource::File { 711 | file: "./good-people.graphql".into(), 712 | }, 713 | }, 714 | ), 715 | ( 716 | "robots".to_string(), 717 | SubgraphConfig { 718 | routing_url: Some("https://robots.example.com".to_string()), 719 | schema: SchemaSource::File { 720 | file: "./better-robots.graphql".into(), 721 | }, 722 | }, 723 | ), 724 | ]); 725 | 726 | assert_eq!(base_config.subgraphs, expected_subgraphs); 727 | } 728 | 729 | #[test] 730 | fn test_supergraph_config_from_iterator() { 731 | let iter = [( 732 | "subgraph_tmp".to_string(), 733 | SubgraphConfig { 734 | routing_url: Some("url".to_string()), 735 | schema: SchemaSource::Sdl { 736 | sdl: "subgraph_tmp".to_string(), 737 | }, 738 | }, 739 | )] 740 | .into_iter(); 741 | 742 | let s: SupergraphConfig = iter.collect(); 743 | assert_eq!(None, s.get_federation_version()); 744 | assert!(s.get_subgraph_definitions().is_ok()); 745 | assert_eq!(1, s.get_subgraph_definitions().unwrap().len()); 746 | } 747 | } 748 | -------------------------------------------------------------------------------- /apollo-composition/src/lib.rs: -------------------------------------------------------------------------------- 1 | use apollo_compiler::{schema::ExtendedType, Schema}; 2 | use apollo_federation::composition::{ 3 | expand_subgraphs, merge_subgraphs, post_merge_validations, pre_merge_validations, 4 | upgrade_subgraphs_if_necessary, validate_satisfiability, Supergraph, 5 | }; 6 | use apollo_federation::connectors::{ 7 | expand::{expand_connectors, Connectors, ExpansionResult}, 8 | validation::{validate, Severity as ValidationSeverity, ValidationResult}, 9 | Connector, 10 | }; 11 | use apollo_federation::internal_composition_api::validate_cache_tag_directives; 12 | use apollo_federation::subgraph::typestate::{Initial, Subgraph, Validated}; 13 | use apollo_federation::subgraph::SubgraphError; 14 | use apollo_federation_types::build_plugin::PluginResult; 15 | use apollo_federation_types::composition::{MergeResult, SubgraphLocation}; 16 | use apollo_federation_types::{ 17 | composition::{Issue, Severity}, 18 | javascript::SubgraphDefinition, 19 | }; 20 | use std::collections::HashMap; 21 | use std::iter::once; 22 | use std::sync::Arc; 23 | 24 | /// This trait includes all the Rust-side composition logic, plus hooks for the JavaScript side. 25 | /// If you implement the functions in this trait to build your own JavaScript interface, then you 26 | /// can call [`HybridComposition::compose`] to run the complete composition process. 27 | /// 28 | /// JavaScript should be implemented using `@apollo/composition@2.9.0-connectors.0`. 29 | #[allow(async_fn_in_trait)] 30 | pub trait HybridComposition { 31 | /// Call the JavaScript `composeServices` function from `@apollo/composition` plus whatever 32 | /// extra logic you need. Make sure to disable satisfiability, like `composeServices(definitions, {runSatisfiability: false})` 33 | async fn compose_services_without_satisfiability( 34 | &mut self, 35 | subgraph_definitions: Vec, 36 | ) -> Option>; 37 | 38 | /// Call the JavaScript `validateSatisfiability` function from `@apollo/composition` plus whatever 39 | /// extra logic you need. 40 | /// 41 | /// # Input 42 | /// 43 | /// The `validateSatisfiability` function wants an argument like `{ supergraphSdl }`. That field 44 | /// should be the value that's updated when [`update_supergraph_sdl`] is called. 45 | /// 46 | /// # Output 47 | /// 48 | /// If satisfiability completes from JavaScript, either a list of hints (could be empty, the Ok case) or a list 49 | /// of errors (never empty, the Err case) will be returned. If Satisfiability _can't_ be run, you can return a single error 50 | /// (`Err(vec![Issue])`) indicating what went wrong. 51 | async fn validate_satisfiability(&mut self) -> Result, Vec>; 52 | 53 | /// Allows the Rust composition code to modify the stored supergraph SDL 54 | /// (for example, to expand connectors). 55 | fn update_supergraph_sdl(&mut self, supergraph_sdl: String); 56 | 57 | /// When the Rust composition/validation code finds issues, it will call this method to add 58 | /// them to the list of issues that will be returned to the user. 59 | /// 60 | /// It's on the implementor of this trait to convert `From` 61 | fn add_issues>(&mut self, issues: Source); 62 | 63 | /// Runs the complete composition process, hooking into both the Rust and JavaScript implementations. 64 | /// 65 | /// # Asyncness 66 | /// 67 | /// While this function is async to allow for flexible JavaScript execution, it is a CPU-heavy task. 68 | /// Take care when consuming this in an async context, as it may block longer than desired. 69 | /// 70 | /// # Algorithm 71 | /// 72 | /// 1. Run Rust-based validation on the subgraphs 73 | /// 2. Call [`compose_services_without_satisfiability`] to run JavaScript-based composition 74 | /// 3. Run Rust-based validation on the supergraph 75 | /// 4. Call [`validate_satisfiability`] to run JavaScript-based validation on the supergraph 76 | async fn compose(&mut self, subgraph_definitions: Vec) { 77 | // `@cacheTag` directive validation 78 | if let Err(cache_tag_errors) = validate_cache_tag_in_subgraphs(&subgraph_definitions) { 79 | self.add_issues(cache_tag_errors.into_iter()); 80 | return; 81 | } 82 | 83 | // connectors subgraph validations 84 | let ConnectorsValidationResult { 85 | subgraphs, 86 | parsed_subgraphs, 87 | hints: connector_hints, 88 | } = match validate_connector_subgraphs(subgraph_definitions) { 89 | Ok(results) => results, 90 | Err(errors) => { 91 | self.add_issues(errors.into_iter()); 92 | return; 93 | } 94 | }; 95 | self.add_issues(connector_hints.into_iter()); 96 | 97 | let Some(supergraph_sdl) = self 98 | .compose_services_without_satisfiability(subgraphs) 99 | .await 100 | else { 101 | return; 102 | }; 103 | 104 | // Any issues with overrides are fatal since they'll cause errors in expansion, 105 | // so we return early if we see any. 106 | let override_errors = validate_overrides(parsed_subgraphs); 107 | if !override_errors.is_empty() { 108 | self.add_issues(override_errors.into_iter()); 109 | return; 110 | } 111 | 112 | let expansion_result = match expand_connectors(supergraph_sdl, &Default::default()) { 113 | Ok(result) => result, 114 | Err(err) => { 115 | self.add_issues(once(Issue { 116 | code: "INTERNAL_ERROR".to_string(), 117 | message: format!( 118 | "Composition failed due to an internal error when expanding connectors, please report this: {err}" 119 | ), 120 | locations: vec![], 121 | severity: Severity::Error, 122 | })); 123 | return; 124 | } 125 | }; 126 | match expansion_result { 127 | ExpansionResult::Expanded { 128 | raw_sdl, 129 | connectors: Connectors { 130 | by_service_name, .. 131 | }, 132 | .. 133 | } => { 134 | let original_supergraph_sdl = supergraph_sdl.to_string(); 135 | self.update_supergraph_sdl(raw_sdl); 136 | let satisfiability_result = self.validate_satisfiability().await; 137 | self.add_issues( 138 | satisfiability_result_into_issues(satisfiability_result).map(|mut issue| { 139 | sanitize_connectors_issue(&mut issue, by_service_name.iter()); 140 | issue 141 | }), 142 | ); 143 | 144 | self.update_supergraph_sdl(original_supergraph_sdl); 145 | } 146 | ExpansionResult::Unchanged => { 147 | let satisfiability_result = self.validate_satisfiability().await; 148 | self.add_issues(satisfiability_result_into_issues(satisfiability_result)); 149 | } 150 | } 151 | } 152 | 153 | ///
*** EXPERIMENTAL ***
154 | /// 155 | /// Runs the composition process with granular composition phases that allow replacing individual 156 | /// steps with Rust and/or JavaScript implementations. 157 | /// 158 | /// 1. subgraph validation 159 | /// 2. Initialize subgraphs - parses SDL into a GraphQL schema 160 | /// 3. Expands subgraphs - adds all missing federation definitions 161 | /// 4. Upgrade subgraphs - upgrades fed v1 schemas to fed v2 162 | /// 5. Validate subgraphs 163 | /// 6. Pre-merge validations (includes connectors validations) 164 | /// 7. Merge subgraphs into a supergrpah 165 | /// 8. Post merge validations 166 | /// 9. expand supergraph 167 | /// 10. Validate satisfiability 168 | /// 169 | /// In case of a composition failure, we return a list of errors from the current composition 170 | /// phase. 171 | async fn experimental_compose( 172 | mut self, 173 | subgraph_definitions: Vec, 174 | ) -> Result> 175 | where 176 | Self: Sized, 177 | { 178 | // `@cacheTag` directive validation 179 | validate_cache_tag_in_subgraphs(&subgraph_definitions)?; 180 | 181 | // connectors validations 182 | // Any issues with overrides are fatal since they'll cause errors in expansion, 183 | // so we return early if we see any. 184 | // TODO those validations should be moved to subgraph validations in the apollo-federation crate instead 185 | let ConnectorsValidationResult { 186 | subgraphs: connected_subgraphs, 187 | parsed_subgraphs, 188 | hints: connector_hints, 189 | } = validate_connector_subgraphs(subgraph_definitions)?; 190 | 191 | let upgraded_subgraphs = self 192 | .experimental_upgrade_subgraphs(connected_subgraphs) 193 | .await?; 194 | 195 | // merge 196 | let merge_result = self 197 | .experimental_merge_subgraphs(upgraded_subgraphs) 198 | .await?; 199 | 200 | // Extra connectors validation after merging. 201 | // - So that connectors-related override errors will only be reported if merging was 202 | // successful. 203 | let override_errors = validate_overrides(parsed_subgraphs); 204 | if !override_errors.is_empty() { 205 | return Err(override_errors); 206 | } 207 | 208 | // expand connectors as needed 209 | let supergraph_sdl = merge_result.supergraph.clone(); 210 | let expansion_result = match expand_connectors(&supergraph_sdl, &Default::default()) { 211 | Ok(result) => result, 212 | Err(err) => { 213 | return Err(vec![err.into()]); 214 | } 215 | }; 216 | 217 | // verify satisfiability 218 | match expansion_result { 219 | ExpansionResult::Expanded { 220 | raw_sdl, 221 | connectors: Connectors { 222 | by_service_name, .. 223 | }, 224 | .. 225 | } => { 226 | self.experimental_validate_satisfiability(raw_sdl.as_str()) 227 | .await 228 | .map(|s| { 229 | let mut composition_hints = merge_result.hints; 230 | composition_hints.extend(s); 231 | 232 | let mut build_messages: Vec<_> = 233 | connector_hints.into_iter().map(|h| h.into()).collect(); 234 | build_messages.extend(composition_hints.into_iter().map(|h| { 235 | let mut issue = Into::::into(h); 236 | sanitize_connectors_issue(&mut issue, by_service_name.iter()); 237 | issue.into() 238 | })); 239 | // return original supergraph 240 | PluginResult::new(Ok(supergraph_sdl), build_messages) 241 | }) 242 | .map_err(|err| { 243 | err.into_iter() 244 | .map(|mut issue| { 245 | sanitize_connectors_issue(&mut issue, by_service_name.iter()); 246 | issue 247 | }) 248 | .collect() 249 | }) 250 | } 251 | ExpansionResult::Unchanged => self 252 | .experimental_validate_satisfiability(supergraph_sdl.as_str()) 253 | .await 254 | .map(|s| { 255 | let mut hints = merge_result.hints; 256 | hints.extend(s); 257 | 258 | let build_messages: Vec<_> = hints 259 | .into_iter() 260 | .map(|h| Into::::into(h).into()) 261 | .collect(); 262 | PluginResult::new(Ok(supergraph_sdl), build_messages) 263 | }), 264 | } 265 | } 266 | 267 | /// Maps to buildSubgraph & upgradeSubgraphsIfNecessary and performs following steps 268 | /// 269 | /// 1. Parses raw SDL schemas into Subgraph 270 | /// 2. Adds missing federation definitions to the subgraph schemas 271 | /// 3. Upgrades federation v1 subgraphs to federation v2 schemas. 272 | /// This is a no-op if it is already a federation v2 subgraph. 273 | /// 4. Validates the expanded/upgraded subgraph schemas. 274 | async fn experimental_upgrade_subgraphs( 275 | &mut self, 276 | subgraphs: Vec, 277 | ) -> Result, Vec> { 278 | let mut issues: Vec = vec![]; 279 | let initial: Vec> = subgraphs 280 | .into_iter() 281 | .map(|s| s.try_into()) 282 | .filter_map(|r| { 283 | r.map_err(|e: SubgraphError| issues.extend(convert_subgraph_error_to_issues(e))) 284 | .ok() 285 | }) 286 | .collect(); 287 | if !issues.is_empty() { 288 | return Err(issues); 289 | } 290 | expand_subgraphs(initial) 291 | .and_then(upgrade_subgraphs_if_necessary) 292 | .map(|subgraphs| subgraphs.into_iter().map(|s| s.into()).collect()) 293 | .map_err(|errors| errors.into_iter().map(Issue::from).collect::>()) 294 | } 295 | 296 | /// In case of a merge failure, returns a list of errors. 297 | async fn experimental_merge_subgraphs( 298 | &mut self, 299 | subgraphs: Vec, 300 | ) -> Result> { 301 | let mut subgraph_errors = vec![]; 302 | let validated: Vec> = subgraphs 303 | .into_iter() 304 | .map(assume_subgraph_validated) 305 | .filter_map(|r| { 306 | r.map_err(|e| subgraph_errors.extend(convert_subgraph_error_to_issues(e))) 307 | .ok() 308 | }) 309 | .collect(); 310 | if !subgraph_errors.is_empty() { 311 | // this should never happen 312 | return Err(subgraph_errors); 313 | } 314 | pre_merge_validations(&validated) 315 | .map_err(|errors| errors.into_iter().map(Issue::from).collect::>())?; 316 | let supergraph = merge_subgraphs(validated) 317 | .map_err(|errors| errors.into_iter().map(Issue::from).collect::>())?; 318 | post_merge_validations(&supergraph) 319 | .map_err(|errors| errors.into_iter().map(Issue::from).collect::>())?; 320 | let hints = supergraph 321 | .hints() 322 | .iter() 323 | .map(|hint| hint.clone().into()) 324 | .collect(); 325 | Ok(MergeResult { 326 | supergraph: supergraph.schema().to_string(), 327 | hints, 328 | }) 329 | } 330 | 331 | /// If successful, returns a list of hints (possibly empty); Otherwise, returns a list of errors. 332 | async fn experimental_validate_satisfiability( 333 | &mut self, 334 | supergraph_sdl: &str, 335 | ) -> Result, Vec> { 336 | let supergraph = Supergraph::parse(supergraph_sdl).map_err(|e| vec![Issue::from(e)])?; 337 | validate_satisfiability(supergraph) 338 | .map(|s| s.hints().iter().map(|h| h.clone().into()).collect()) 339 | .map_err(|errors| errors.into_iter().map(Issue::from).collect::>()) 340 | } 341 | } 342 | 343 | struct SubgraphSchema { 344 | schema: Schema, 345 | has_connectors: bool, 346 | } 347 | 348 | struct ConnectorsValidationResult { 349 | subgraphs: Vec, 350 | parsed_subgraphs: HashMap, 351 | hints: Vec, 352 | } 353 | // TODO this should eventually move under expand/validate subgraph logic 354 | fn validate_connector_subgraphs( 355 | subgraph_definitions: Vec, 356 | ) -> Result> { 357 | let mut subgraph_validation_errors = Vec::new(); 358 | let mut subgraph_validation_hints = Vec::new(); 359 | let mut parsed_schemas = HashMap::new(); 360 | let subgraph_definitions = subgraph_definitions 361 | .into_iter() 362 | .map(|mut subgraph| { 363 | let ValidationResult { 364 | errors, 365 | has_connectors, 366 | schema, 367 | transformed, 368 | } = validate(subgraph.sdl, &subgraph.name); 369 | subgraph.sdl = transformed; 370 | for error in errors { 371 | let issue = Issue { 372 | code: error.code.to_string(), 373 | message: error.message, 374 | locations: error 375 | .locations 376 | .into_iter() 377 | .map(|range| SubgraphLocation { 378 | subgraph: Some(subgraph.name.clone()), 379 | range: Some(range), 380 | }) 381 | .collect(), 382 | severity: convert_severity(error.code.severity()), 383 | }; 384 | if issue.severity == Severity::Error { 385 | subgraph_validation_errors.push(issue); 386 | } else { 387 | subgraph_validation_hints.push(issue); 388 | } 389 | } 390 | parsed_schemas.insert( 391 | subgraph.name.clone(), 392 | SubgraphSchema { 393 | schema, 394 | has_connectors, 395 | }, 396 | ); 397 | subgraph 398 | }) 399 | .collect(); 400 | 401 | if !subgraph_validation_errors.is_empty() { 402 | return Err(subgraph_validation_errors); 403 | } 404 | Ok(ConnectorsValidationResult { 405 | subgraphs: subgraph_definitions, 406 | parsed_subgraphs: parsed_schemas, 407 | hints: subgraph_validation_hints, 408 | }) 409 | } 410 | 411 | /// Validate overrides for connector-related subgraphs 412 | /// 413 | /// Overrides mess with the supergraph in ways that can be difficult to detect when 414 | /// expanding connectors; the supergraph may omit overridden fields and other shenanigans. 415 | /// To allow for a better developer experience, we check here if any connector-enabled subgraphs 416 | /// have fields overridden. 417 | fn validate_overrides(schemas: HashMap) -> Vec { 418 | let mut override_errors = Vec::new(); 419 | for (subgraph_name, SubgraphSchema { schema, .. }) in &schemas { 420 | // We need to grab all fields in the schema since only fields can have the @override 421 | // directive attached 422 | macro_rules! extract_directives { 423 | ($node:ident) => { 424 | $node 425 | .fields 426 | .iter() 427 | .flat_map(|(name, field)| { 428 | field 429 | .directives 430 | .iter() 431 | .map(move |d| (format!("{}.{}", $node.name, name), d)) 432 | }) 433 | .collect::>() 434 | }; 435 | } 436 | 437 | let override_directives = schema 438 | .types 439 | .values() 440 | .flat_map(|v| match v { 441 | ExtendedType::Object(node) => extract_directives!(node), 442 | ExtendedType::Interface(node) => extract_directives!(node), 443 | ExtendedType::InputObject(node) => extract_directives!(node), 444 | 445 | // These types do not have fields 446 | ExtendedType::Scalar(_) | ExtendedType::Union(_) | ExtendedType::Enum(_) => { 447 | Vec::new() 448 | } 449 | }) 450 | .filter(|(_, directive)| { 451 | // TODO: The directive name for @override could have been aliased 452 | // at the SDL level, so we'll need to extract the aliased name here instead 453 | directive.name == "override" || directive.name == "federation__override" 454 | }); 455 | 456 | // Now see if we have any overrides that try to reference connector subgraphs 457 | for (field, directive) in override_directives { 458 | // If the override directive does not have a valid `from` field, then there is 459 | // no point trying to validate it, as later steps will validate the entire schema. 460 | let Ok(Some(overridden_subgraph_name)) = directive 461 | .argument_by_name("from", schema) 462 | .map(|node| node.as_str()) 463 | else { 464 | continue; 465 | }; 466 | 467 | if schemas 468 | .get(overridden_subgraph_name) 469 | .is_some_and(|schema| schema.has_connectors) 470 | { 471 | override_errors.push(Issue { 472 | code: "OVERRIDE_ON_CONNECTOR".to_string(), 473 | message: format!( 474 | r#"Field "{field}" on subgraph "{subgraph_name}" is trying to override connector-enabled subgraph "{overridden_subgraph_name}", which is not yet supported. See https://go.apollo.dev/connectors/limitations#override-is-partially-unsupported"#, 475 | ), 476 | locations: vec![SubgraphLocation { 477 | subgraph: Some(String::from(overridden_subgraph_name)), 478 | range: directive.line_column_range(&schema.sources), 479 | }], 480 | severity: Severity::Error, 481 | }); 482 | } 483 | } 484 | } 485 | 486 | override_errors 487 | } 488 | 489 | fn sanitize_connectors_issue<'a>( 490 | issue: &mut Issue, 491 | connector_subgraphs: impl Iterator, &'a Connector)>, 492 | ) { 493 | for (service_name, connector) in connector_subgraphs { 494 | issue.message = issue 495 | .message 496 | .replace(&**service_name, connector.id.subgraph_name.as_str()); 497 | } 498 | } 499 | 500 | fn validate_cache_tag_in_subgraphs( 501 | subgraph_definitions: &[SubgraphDefinition], 502 | ) -> Result<(), Vec> { 503 | let mut issues = Vec::new(); 504 | for subgraph_def in subgraph_definitions { 505 | match validate_cache_tag_directives( 506 | &subgraph_def.name, 507 | &subgraph_def.url, 508 | &subgraph_def.sdl, 509 | ) { 510 | Err(_err) => { 511 | // Ignore internal errors as they must be GraphQL/Federation validation errors, 512 | // which will be reported during the main validation. 513 | break; 514 | } 515 | Ok(res) => { 516 | if !res.errors.is_empty() { 517 | issues.extend(res.errors.into_iter().map(|err| { 518 | Issue { 519 | code: err.code(), 520 | message: err.message(), 521 | locations: err 522 | .locations 523 | .into_iter() 524 | .map(|range| SubgraphLocation { 525 | subgraph: Some(subgraph_def.name.clone()), 526 | range: Some(range), 527 | }) 528 | .collect(), 529 | severity: Severity::Error, 530 | } 531 | })); 532 | } 533 | } 534 | } 535 | } 536 | if !issues.is_empty() { 537 | Err(issues) 538 | } else { 539 | Ok(()) 540 | } 541 | } 542 | 543 | pub type SupergraphSdl<'a> = &'a str; 544 | 545 | /// A successfully composed supergraph, optionally with some issues that should be addressed. 546 | #[derive(Clone, Debug)] 547 | pub struct PartialSuccess { 548 | pub supergraph_sdl: String, 549 | pub issues: Vec, 550 | } 551 | 552 | fn convert_severity(severity: ValidationSeverity) -> Severity { 553 | match severity { 554 | ValidationSeverity::Error => Severity::Error, 555 | ValidationSeverity::Warning => Severity::Warning, 556 | } 557 | } 558 | 559 | fn satisfiability_result_into_issues( 560 | result: Result, Vec>, 561 | ) -> impl Iterator { 562 | match result { 563 | Ok(hints) => hints.into_iter(), 564 | Err(errors) => errors.into_iter(), 565 | } 566 | } 567 | 568 | // converts subgraph definitions to Subgraph by assuming schema is already 569 | // expanded/upgraded/validated 570 | fn assume_subgraph_validated( 571 | definition: SubgraphDefinition, 572 | ) -> Result, SubgraphError> { 573 | Subgraph::parse( 574 | definition.name.as_str(), 575 | definition.url.as_str(), 576 | definition.sdl.as_str(), 577 | ) 578 | .and_then(|s| s.assume_expanded()) 579 | .map(|s| s.assume_validated()) 580 | } 581 | 582 | fn convert_subgraph_error_to_issues(error: SubgraphError) -> Vec { 583 | error 584 | .to_composition_errors() 585 | .map(|err| err.into()) 586 | .collect() 587 | } 588 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "ahash" 7 | version = "0.8.12" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" 10 | dependencies = [ 11 | "cfg-if", 12 | "getrandom", 13 | "once_cell", 14 | "version_check", 15 | "zerocopy", 16 | ] 17 | 18 | [[package]] 19 | name = "aho-corasick" 20 | version = "1.1.3" 21 | source = "registry+https://github.com/rust-lang/crates.io-index" 22 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 23 | dependencies = [ 24 | "memchr", 25 | ] 26 | 27 | [[package]] 28 | name = "allocator-api2" 29 | version = "0.2.21" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" 32 | 33 | [[package]] 34 | name = "anstyle" 35 | version = "1.0.11" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" 38 | 39 | [[package]] 40 | name = "apollo-compiler" 41 | version = "1.30.0" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "87e4c0116cde9e3e5679806cf91c464d9efb7f1e231abffc505e0f6d4b911260" 44 | dependencies = [ 45 | "ahash", 46 | "apollo-parser", 47 | "ariadne", 48 | "futures", 49 | "indexmap 2.11.4", 50 | "rowan", 51 | "serde", 52 | "serde_json_bytes", 53 | "thiserror 2.0.16", 54 | "triomphe", 55 | "typed-arena", 56 | ] 57 | 58 | [[package]] 59 | name = "apollo-composition" 60 | version = "0.4.1" 61 | dependencies = [ 62 | "apollo-compiler", 63 | "apollo-federation", 64 | "apollo-federation-types", 65 | ] 66 | 67 | [[package]] 68 | name = "apollo-federation" 69 | version = "2.8.2" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "69d477e2b45ac1c2c824f5ee4e538fb33143aa2b507a0ec0960f8576614955bd" 72 | dependencies = [ 73 | "apollo-compiler", 74 | "derive_more", 75 | "either", 76 | "encoding_rs", 77 | "form_urlencoded", 78 | "hashbrown 0.16.0", 79 | "http", 80 | "indexmap 2.11.4", 81 | "itertools", 82 | "levenshtein", 83 | "line-col", 84 | "mime", 85 | "multi_try", 86 | "multimap", 87 | "nom", 88 | "nom_locate", 89 | "parking_lot", 90 | "percent-encoding", 91 | "petgraph", 92 | "regex", 93 | "serde", 94 | "serde_json", 95 | "serde_json_bytes", 96 | "shape", 97 | "strum", 98 | "strum_macros", 99 | "thiserror 2.0.16", 100 | "time", 101 | "tracing", 102 | "url", 103 | ] 104 | 105 | [[package]] 106 | name = "apollo-federation-types" 107 | version = "0.16.1" 108 | dependencies = [ 109 | "apollo-compiler", 110 | "apollo-federation", 111 | "assert_fs", 112 | "log", 113 | "rstest", 114 | "schemars", 115 | "semver", 116 | "serde", 117 | "serde_json", 118 | "serde_with", 119 | "serde_yaml", 120 | "thiserror 1.0.69", 121 | "url", 122 | ] 123 | 124 | [[package]] 125 | name = "apollo-parser" 126 | version = "0.8.4" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "c8f05cbc7da3c2e3bb2f86e985aad5f72571d2e2cd26faf8caa7782131576f84" 129 | dependencies = [ 130 | "memchr", 131 | "rowan", 132 | "thiserror 1.0.69", 133 | ] 134 | 135 | [[package]] 136 | name = "ariadne" 137 | version = "0.5.1" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "36f5e3dca4e09a6f340a61a0e9c7b61e030c69fc27bf29d73218f7e5e3b7638f" 140 | dependencies = [ 141 | "concolor", 142 | "unicode-width", 143 | "yansi", 144 | ] 145 | 146 | [[package]] 147 | name = "assert_fs" 148 | version = "1.1.3" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "a652f6cb1f516886fcfee5e7a5c078b9ade62cfcb889524efe5a64d682dd27a9" 151 | dependencies = [ 152 | "anstyle", 153 | "doc-comment", 154 | "globwalk", 155 | "predicates", 156 | "predicates-core", 157 | "predicates-tree", 158 | "tempfile", 159 | ] 160 | 161 | [[package]] 162 | name = "autocfg" 163 | version = "1.5.0" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" 166 | 167 | [[package]] 168 | name = "bitflags" 169 | version = "1.3.2" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 172 | 173 | [[package]] 174 | name = "bitflags" 175 | version = "2.9.4" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" 178 | 179 | [[package]] 180 | name = "block-buffer" 181 | version = "0.10.4" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 184 | dependencies = [ 185 | "generic-array", 186 | ] 187 | 188 | [[package]] 189 | name = "bstr" 190 | version = "1.12.0" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" 193 | dependencies = [ 194 | "memchr", 195 | "serde", 196 | ] 197 | 198 | [[package]] 199 | name = "bytecount" 200 | version = "0.6.9" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" 203 | 204 | [[package]] 205 | name = "bytes" 206 | version = "1.10.1" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" 209 | 210 | [[package]] 211 | name = "cfg-if" 212 | version = "1.0.3" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" 215 | 216 | [[package]] 217 | name = "concolor" 218 | version = "0.1.1" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "0b946244a988c390a94667ae0e3958411fa40cc46ea496a929b263d883f5f9c3" 221 | dependencies = [ 222 | "bitflags 1.3.2", 223 | "concolor-query", 224 | "is-terminal", 225 | ] 226 | 227 | [[package]] 228 | name = "concolor-query" 229 | version = "0.3.3" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "88d11d52c3d7ca2e6d0040212be9e4dbbcd78b6447f535b6b561f449427944cf" 232 | dependencies = [ 233 | "windows-sys 0.45.0", 234 | ] 235 | 236 | [[package]] 237 | name = "convert_case" 238 | version = "0.7.1" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7" 241 | dependencies = [ 242 | "unicode-segmentation", 243 | ] 244 | 245 | [[package]] 246 | name = "countme" 247 | version = "3.0.1" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636" 250 | 251 | [[package]] 252 | name = "cpufeatures" 253 | version = "0.2.17" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 256 | dependencies = [ 257 | "libc", 258 | ] 259 | 260 | [[package]] 261 | name = "crossbeam-deque" 262 | version = "0.8.6" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" 265 | dependencies = [ 266 | "crossbeam-epoch", 267 | "crossbeam-utils", 268 | ] 269 | 270 | [[package]] 271 | name = "crossbeam-epoch" 272 | version = "0.9.18" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" 275 | dependencies = [ 276 | "crossbeam-utils", 277 | ] 278 | 279 | [[package]] 280 | name = "crossbeam-utils" 281 | version = "0.8.21" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 284 | 285 | [[package]] 286 | name = "crypto-common" 287 | version = "0.1.6" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 290 | dependencies = [ 291 | "generic-array", 292 | "typenum", 293 | ] 294 | 295 | [[package]] 296 | name = "darling" 297 | version = "0.21.3" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" 300 | dependencies = [ 301 | "darling_core", 302 | "darling_macro", 303 | ] 304 | 305 | [[package]] 306 | name = "darling_core" 307 | version = "0.21.3" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" 310 | dependencies = [ 311 | "fnv", 312 | "ident_case", 313 | "proc-macro2", 314 | "quote", 315 | "strsim", 316 | "syn", 317 | ] 318 | 319 | [[package]] 320 | name = "darling_macro" 321 | version = "0.21.3" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" 324 | dependencies = [ 325 | "darling_core", 326 | "quote", 327 | "syn", 328 | ] 329 | 330 | [[package]] 331 | name = "deranged" 332 | version = "0.5.4" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" 335 | dependencies = [ 336 | "powerfmt", 337 | ] 338 | 339 | [[package]] 340 | name = "derive_more" 341 | version = "2.0.1" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" 344 | dependencies = [ 345 | "derive_more-impl", 346 | ] 347 | 348 | [[package]] 349 | name = "derive_more-impl" 350 | version = "2.0.1" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" 353 | dependencies = [ 354 | "convert_case", 355 | "proc-macro2", 356 | "quote", 357 | "syn", 358 | "unicode-xid", 359 | ] 360 | 361 | [[package]] 362 | name = "difflib" 363 | version = "0.4.0" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" 366 | 367 | [[package]] 368 | name = "digest" 369 | version = "0.10.7" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 372 | dependencies = [ 373 | "block-buffer", 374 | "crypto-common", 375 | ] 376 | 377 | [[package]] 378 | name = "displaydoc" 379 | version = "0.2.5" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 382 | dependencies = [ 383 | "proc-macro2", 384 | "quote", 385 | "syn", 386 | ] 387 | 388 | [[package]] 389 | name = "doc-comment" 390 | version = "0.3.3" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" 393 | 394 | [[package]] 395 | name = "dyn-clone" 396 | version = "1.0.20" 397 | source = "registry+https://github.com/rust-lang/crates.io-index" 398 | checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" 399 | 400 | [[package]] 401 | name = "either" 402 | version = "1.15.0" 403 | source = "registry+https://github.com/rust-lang/crates.io-index" 404 | checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" 405 | 406 | [[package]] 407 | name = "encoding_rs" 408 | version = "0.8.35" 409 | source = "registry+https://github.com/rust-lang/crates.io-index" 410 | checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" 411 | dependencies = [ 412 | "cfg-if", 413 | ] 414 | 415 | [[package]] 416 | name = "equivalent" 417 | version = "1.0.2" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 420 | 421 | [[package]] 422 | name = "errno" 423 | version = "0.3.14" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" 426 | dependencies = [ 427 | "libc", 428 | "windows-sys 0.61.0", 429 | ] 430 | 431 | [[package]] 432 | name = "fastrand" 433 | version = "2.3.0" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 436 | 437 | [[package]] 438 | name = "fixedbitset" 439 | version = "0.5.7" 440 | source = "registry+https://github.com/rust-lang/crates.io-index" 441 | checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" 442 | 443 | [[package]] 444 | name = "fnv" 445 | version = "1.0.7" 446 | source = "registry+https://github.com/rust-lang/crates.io-index" 447 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 448 | 449 | [[package]] 450 | name = "foldhash" 451 | version = "0.1.5" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" 454 | 455 | [[package]] 456 | name = "foldhash" 457 | version = "0.2.0" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" 460 | 461 | [[package]] 462 | name = "form_urlencoded" 463 | version = "1.2.2" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" 466 | dependencies = [ 467 | "percent-encoding", 468 | ] 469 | 470 | [[package]] 471 | name = "futures" 472 | version = "0.3.31" 473 | source = "registry+https://github.com/rust-lang/crates.io-index" 474 | checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" 475 | dependencies = [ 476 | "futures-channel", 477 | "futures-core", 478 | "futures-executor", 479 | "futures-io", 480 | "futures-sink", 481 | "futures-task", 482 | "futures-util", 483 | ] 484 | 485 | [[package]] 486 | name = "futures-channel" 487 | version = "0.3.31" 488 | source = "registry+https://github.com/rust-lang/crates.io-index" 489 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 490 | dependencies = [ 491 | "futures-core", 492 | "futures-sink", 493 | ] 494 | 495 | [[package]] 496 | name = "futures-core" 497 | version = "0.3.31" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 500 | 501 | [[package]] 502 | name = "futures-executor" 503 | version = "0.3.31" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" 506 | dependencies = [ 507 | "futures-core", 508 | "futures-task", 509 | "futures-util", 510 | ] 511 | 512 | [[package]] 513 | name = "futures-io" 514 | version = "0.3.31" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 517 | 518 | [[package]] 519 | name = "futures-macro" 520 | version = "0.3.31" 521 | source = "registry+https://github.com/rust-lang/crates.io-index" 522 | checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 523 | dependencies = [ 524 | "proc-macro2", 525 | "quote", 526 | "syn", 527 | ] 528 | 529 | [[package]] 530 | name = "futures-sink" 531 | version = "0.3.31" 532 | source = "registry+https://github.com/rust-lang/crates.io-index" 533 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 534 | 535 | [[package]] 536 | name = "futures-task" 537 | version = "0.3.31" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 540 | 541 | [[package]] 542 | name = "futures-timer" 543 | version = "3.0.3" 544 | source = "registry+https://github.com/rust-lang/crates.io-index" 545 | checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" 546 | 547 | [[package]] 548 | name = "futures-util" 549 | version = "0.3.31" 550 | source = "registry+https://github.com/rust-lang/crates.io-index" 551 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 552 | dependencies = [ 553 | "futures-channel", 554 | "futures-core", 555 | "futures-io", 556 | "futures-macro", 557 | "futures-sink", 558 | "futures-task", 559 | "memchr", 560 | "pin-project-lite", 561 | "pin-utils", 562 | "slab", 563 | ] 564 | 565 | [[package]] 566 | name = "generic-array" 567 | version = "0.14.7" 568 | source = "registry+https://github.com/rust-lang/crates.io-index" 569 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 570 | dependencies = [ 571 | "typenum", 572 | "version_check", 573 | ] 574 | 575 | [[package]] 576 | name = "getrandom" 577 | version = "0.3.3" 578 | source = "registry+https://github.com/rust-lang/crates.io-index" 579 | checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" 580 | dependencies = [ 581 | "cfg-if", 582 | "libc", 583 | "r-efi", 584 | "wasi", 585 | ] 586 | 587 | [[package]] 588 | name = "glob" 589 | version = "0.3.3" 590 | source = "registry+https://github.com/rust-lang/crates.io-index" 591 | checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" 592 | 593 | [[package]] 594 | name = "globset" 595 | version = "0.4.16" 596 | source = "registry+https://github.com/rust-lang/crates.io-index" 597 | checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5" 598 | dependencies = [ 599 | "aho-corasick", 600 | "bstr", 601 | "log", 602 | "regex-automata", 603 | "regex-syntax", 604 | ] 605 | 606 | [[package]] 607 | name = "globwalk" 608 | version = "0.9.1" 609 | source = "registry+https://github.com/rust-lang/crates.io-index" 610 | checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" 611 | dependencies = [ 612 | "bitflags 2.9.4", 613 | "ignore", 614 | "walkdir", 615 | ] 616 | 617 | [[package]] 618 | name = "hashbrown" 619 | version = "0.12.3" 620 | source = "registry+https://github.com/rust-lang/crates.io-index" 621 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 622 | 623 | [[package]] 624 | name = "hashbrown" 625 | version = "0.14.5" 626 | source = "registry+https://github.com/rust-lang/crates.io-index" 627 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 628 | 629 | [[package]] 630 | name = "hashbrown" 631 | version = "0.15.5" 632 | source = "registry+https://github.com/rust-lang/crates.io-index" 633 | checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" 634 | dependencies = [ 635 | "allocator-api2", 636 | "equivalent", 637 | "foldhash 0.1.5", 638 | ] 639 | 640 | [[package]] 641 | name = "hashbrown" 642 | version = "0.16.0" 643 | source = "registry+https://github.com/rust-lang/crates.io-index" 644 | checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" 645 | dependencies = [ 646 | "allocator-api2", 647 | "equivalent", 648 | "foldhash 0.2.0", 649 | ] 650 | 651 | [[package]] 652 | name = "heck" 653 | version = "0.5.0" 654 | source = "registry+https://github.com/rust-lang/crates.io-index" 655 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 656 | 657 | [[package]] 658 | name = "hermit-abi" 659 | version = "0.5.2" 660 | source = "registry+https://github.com/rust-lang/crates.io-index" 661 | checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" 662 | 663 | [[package]] 664 | name = "http" 665 | version = "1.3.1" 666 | source = "registry+https://github.com/rust-lang/crates.io-index" 667 | checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" 668 | dependencies = [ 669 | "bytes", 670 | "fnv", 671 | "itoa", 672 | ] 673 | 674 | [[package]] 675 | name = "icu_collections" 676 | version = "2.0.0" 677 | source = "registry+https://github.com/rust-lang/crates.io-index" 678 | checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" 679 | dependencies = [ 680 | "displaydoc", 681 | "potential_utf", 682 | "yoke", 683 | "zerofrom", 684 | "zerovec", 685 | ] 686 | 687 | [[package]] 688 | name = "icu_locale_core" 689 | version = "2.0.0" 690 | source = "registry+https://github.com/rust-lang/crates.io-index" 691 | checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" 692 | dependencies = [ 693 | "displaydoc", 694 | "litemap", 695 | "tinystr", 696 | "writeable", 697 | "zerovec", 698 | ] 699 | 700 | [[package]] 701 | name = "icu_normalizer" 702 | version = "2.0.0" 703 | source = "registry+https://github.com/rust-lang/crates.io-index" 704 | checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" 705 | dependencies = [ 706 | "displaydoc", 707 | "icu_collections", 708 | "icu_normalizer_data", 709 | "icu_properties", 710 | "icu_provider", 711 | "smallvec", 712 | "zerovec", 713 | ] 714 | 715 | [[package]] 716 | name = "icu_normalizer_data" 717 | version = "2.0.0" 718 | source = "registry+https://github.com/rust-lang/crates.io-index" 719 | checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" 720 | 721 | [[package]] 722 | name = "icu_properties" 723 | version = "2.0.1" 724 | source = "registry+https://github.com/rust-lang/crates.io-index" 725 | checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" 726 | dependencies = [ 727 | "displaydoc", 728 | "icu_collections", 729 | "icu_locale_core", 730 | "icu_properties_data", 731 | "icu_provider", 732 | "potential_utf", 733 | "zerotrie", 734 | "zerovec", 735 | ] 736 | 737 | [[package]] 738 | name = "icu_properties_data" 739 | version = "2.0.1" 740 | source = "registry+https://github.com/rust-lang/crates.io-index" 741 | checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" 742 | 743 | [[package]] 744 | name = "icu_provider" 745 | version = "2.0.0" 746 | source = "registry+https://github.com/rust-lang/crates.io-index" 747 | checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" 748 | dependencies = [ 749 | "displaydoc", 750 | "icu_locale_core", 751 | "stable_deref_trait", 752 | "tinystr", 753 | "writeable", 754 | "yoke", 755 | "zerofrom", 756 | "zerotrie", 757 | "zerovec", 758 | ] 759 | 760 | [[package]] 761 | name = "ident_case" 762 | version = "1.0.1" 763 | source = "registry+https://github.com/rust-lang/crates.io-index" 764 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 765 | 766 | [[package]] 767 | name = "idna" 768 | version = "1.1.0" 769 | source = "registry+https://github.com/rust-lang/crates.io-index" 770 | checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" 771 | dependencies = [ 772 | "idna_adapter", 773 | "smallvec", 774 | "utf8_iter", 775 | ] 776 | 777 | [[package]] 778 | name = "idna_adapter" 779 | version = "1.2.1" 780 | source = "registry+https://github.com/rust-lang/crates.io-index" 781 | checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" 782 | dependencies = [ 783 | "icu_normalizer", 784 | "icu_properties", 785 | ] 786 | 787 | [[package]] 788 | name = "ignore" 789 | version = "0.4.23" 790 | source = "registry+https://github.com/rust-lang/crates.io-index" 791 | checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" 792 | dependencies = [ 793 | "crossbeam-deque", 794 | "globset", 795 | "log", 796 | "memchr", 797 | "regex-automata", 798 | "same-file", 799 | "walkdir", 800 | "winapi-util", 801 | ] 802 | 803 | [[package]] 804 | name = "indexmap" 805 | version = "1.9.3" 806 | source = "registry+https://github.com/rust-lang/crates.io-index" 807 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 808 | dependencies = [ 809 | "autocfg", 810 | "hashbrown 0.12.3", 811 | ] 812 | 813 | [[package]] 814 | name = "indexmap" 815 | version = "2.11.4" 816 | source = "registry+https://github.com/rust-lang/crates.io-index" 817 | checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" 818 | dependencies = [ 819 | "equivalent", 820 | "hashbrown 0.16.0", 821 | "serde", 822 | "serde_core", 823 | ] 824 | 825 | [[package]] 826 | name = "is-terminal" 827 | version = "0.4.16" 828 | source = "registry+https://github.com/rust-lang/crates.io-index" 829 | checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" 830 | dependencies = [ 831 | "hermit-abi", 832 | "libc", 833 | "windows-sys 0.59.0", 834 | ] 835 | 836 | [[package]] 837 | name = "itertools" 838 | version = "0.14.0" 839 | source = "registry+https://github.com/rust-lang/crates.io-index" 840 | checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" 841 | dependencies = [ 842 | "either", 843 | ] 844 | 845 | [[package]] 846 | name = "itoa" 847 | version = "1.0.15" 848 | source = "registry+https://github.com/rust-lang/crates.io-index" 849 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 850 | 851 | [[package]] 852 | name = "jsonpath-rust" 853 | version = "0.3.5" 854 | source = "registry+https://github.com/rust-lang/crates.io-index" 855 | checksum = "06cc127b7c3d270be504572364f9569761a180b981919dd0d87693a7f5fb7829" 856 | dependencies = [ 857 | "pest", 858 | "pest_derive", 859 | "regex", 860 | "serde_json", 861 | "thiserror 1.0.69", 862 | ] 863 | 864 | [[package]] 865 | name = "levenshtein" 866 | version = "1.0.5" 867 | source = "registry+https://github.com/rust-lang/crates.io-index" 868 | checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" 869 | 870 | [[package]] 871 | name = "libc" 872 | version = "0.2.176" 873 | source = "registry+https://github.com/rust-lang/crates.io-index" 874 | checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" 875 | 876 | [[package]] 877 | name = "line-col" 878 | version = "0.2.1" 879 | source = "registry+https://github.com/rust-lang/crates.io-index" 880 | checksum = "9e69cdf6b85b5c8dce514f694089a2cf8b1a702f6cd28607bcb3cf296c9778db" 881 | 882 | [[package]] 883 | name = "linked-hash-map" 884 | version = "0.5.6" 885 | source = "registry+https://github.com/rust-lang/crates.io-index" 886 | checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" 887 | 888 | [[package]] 889 | name = "linux-raw-sys" 890 | version = "0.11.0" 891 | source = "registry+https://github.com/rust-lang/crates.io-index" 892 | checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" 893 | 894 | [[package]] 895 | name = "litemap" 896 | version = "0.8.0" 897 | source = "registry+https://github.com/rust-lang/crates.io-index" 898 | checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" 899 | 900 | [[package]] 901 | name = "lock_api" 902 | version = "0.4.13" 903 | source = "registry+https://github.com/rust-lang/crates.io-index" 904 | checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" 905 | dependencies = [ 906 | "autocfg", 907 | "scopeguard", 908 | ] 909 | 910 | [[package]] 911 | name = "log" 912 | version = "0.4.28" 913 | source = "registry+https://github.com/rust-lang/crates.io-index" 914 | checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" 915 | 916 | [[package]] 917 | name = "memchr" 918 | version = "2.7.6" 919 | source = "registry+https://github.com/rust-lang/crates.io-index" 920 | checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" 921 | 922 | [[package]] 923 | name = "mime" 924 | version = "0.3.17" 925 | source = "registry+https://github.com/rust-lang/crates.io-index" 926 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 927 | 928 | [[package]] 929 | name = "minimal-lexical" 930 | version = "0.2.1" 931 | source = "registry+https://github.com/rust-lang/crates.io-index" 932 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 933 | 934 | [[package]] 935 | name = "multi_try" 936 | version = "0.3.0" 937 | source = "registry+https://github.com/rust-lang/crates.io-index" 938 | checksum = "b42256e8ab5f19108cf42e2762786052ae4660635f6fe76134d2cab37068ee8a" 939 | 940 | [[package]] 941 | name = "multimap" 942 | version = "0.10.1" 943 | source = "registry+https://github.com/rust-lang/crates.io-index" 944 | checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" 945 | dependencies = [ 946 | "serde", 947 | ] 948 | 949 | [[package]] 950 | name = "nom" 951 | version = "7.1.3" 952 | source = "registry+https://github.com/rust-lang/crates.io-index" 953 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 954 | dependencies = [ 955 | "memchr", 956 | "minimal-lexical", 957 | ] 958 | 959 | [[package]] 960 | name = "nom_locate" 961 | version = "4.2.0" 962 | source = "registry+https://github.com/rust-lang/crates.io-index" 963 | checksum = "1e3c83c053b0713da60c5b8de47fe8e494fe3ece5267b2f23090a07a053ba8f3" 964 | dependencies = [ 965 | "bytecount", 966 | "memchr", 967 | "nom", 968 | ] 969 | 970 | [[package]] 971 | name = "num-conv" 972 | version = "0.1.0" 973 | source = "registry+https://github.com/rust-lang/crates.io-index" 974 | checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 975 | 976 | [[package]] 977 | name = "num_threads" 978 | version = "0.1.7" 979 | source = "registry+https://github.com/rust-lang/crates.io-index" 980 | checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" 981 | dependencies = [ 982 | "libc", 983 | ] 984 | 985 | [[package]] 986 | name = "once_cell" 987 | version = "1.21.3" 988 | source = "registry+https://github.com/rust-lang/crates.io-index" 989 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 990 | 991 | [[package]] 992 | name = "parking_lot" 993 | version = "0.12.4" 994 | source = "registry+https://github.com/rust-lang/crates.io-index" 995 | checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" 996 | dependencies = [ 997 | "lock_api", 998 | "parking_lot_core", 999 | ] 1000 | 1001 | [[package]] 1002 | name = "parking_lot_core" 1003 | version = "0.9.11" 1004 | source = "registry+https://github.com/rust-lang/crates.io-index" 1005 | checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" 1006 | dependencies = [ 1007 | "cfg-if", 1008 | "libc", 1009 | "redox_syscall", 1010 | "smallvec", 1011 | "windows-targets 0.52.6", 1012 | ] 1013 | 1014 | [[package]] 1015 | name = "percent-encoding" 1016 | version = "2.3.2" 1017 | source = "registry+https://github.com/rust-lang/crates.io-index" 1018 | checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" 1019 | 1020 | [[package]] 1021 | name = "pest" 1022 | version = "2.8.2" 1023 | source = "registry+https://github.com/rust-lang/crates.io-index" 1024 | checksum = "21e0a3a33733faeaf8651dfee72dd0f388f0c8e5ad496a3478fa5a922f49cfa8" 1025 | dependencies = [ 1026 | "memchr", 1027 | "thiserror 2.0.16", 1028 | "ucd-trie", 1029 | ] 1030 | 1031 | [[package]] 1032 | name = "pest_derive" 1033 | version = "2.8.2" 1034 | source = "registry+https://github.com/rust-lang/crates.io-index" 1035 | checksum = "bc58706f770acb1dbd0973e6530a3cff4746fb721207feb3a8a6064cd0b6c663" 1036 | dependencies = [ 1037 | "pest", 1038 | "pest_generator", 1039 | ] 1040 | 1041 | [[package]] 1042 | name = "pest_generator" 1043 | version = "2.8.2" 1044 | source = "registry+https://github.com/rust-lang/crates.io-index" 1045 | checksum = "6d4f36811dfe07f7b8573462465d5cb8965fffc2e71ae377a33aecf14c2c9a2f" 1046 | dependencies = [ 1047 | "pest", 1048 | "pest_meta", 1049 | "proc-macro2", 1050 | "quote", 1051 | "syn", 1052 | ] 1053 | 1054 | [[package]] 1055 | name = "pest_meta" 1056 | version = "2.8.2" 1057 | source = "registry+https://github.com/rust-lang/crates.io-index" 1058 | checksum = "42919b05089acbd0a5dcd5405fb304d17d1053847b81163d09c4ad18ce8e8420" 1059 | dependencies = [ 1060 | "pest", 1061 | "sha2", 1062 | ] 1063 | 1064 | [[package]] 1065 | name = "petgraph" 1066 | version = "0.8.2" 1067 | source = "registry+https://github.com/rust-lang/crates.io-index" 1068 | checksum = "54acf3a685220b533e437e264e4d932cfbdc4cc7ec0cd232ed73c08d03b8a7ca" 1069 | dependencies = [ 1070 | "fixedbitset", 1071 | "hashbrown 0.15.5", 1072 | "indexmap 2.11.4", 1073 | "serde", 1074 | "serde_derive", 1075 | ] 1076 | 1077 | [[package]] 1078 | name = "pin-project-lite" 1079 | version = "0.2.16" 1080 | source = "registry+https://github.com/rust-lang/crates.io-index" 1081 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 1082 | 1083 | [[package]] 1084 | name = "pin-utils" 1085 | version = "0.1.0" 1086 | source = "registry+https://github.com/rust-lang/crates.io-index" 1087 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1088 | 1089 | [[package]] 1090 | name = "potential_utf" 1091 | version = "0.1.3" 1092 | source = "registry+https://github.com/rust-lang/crates.io-index" 1093 | checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" 1094 | dependencies = [ 1095 | "zerovec", 1096 | ] 1097 | 1098 | [[package]] 1099 | name = "powerfmt" 1100 | version = "0.2.0" 1101 | source = "registry+https://github.com/rust-lang/crates.io-index" 1102 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 1103 | 1104 | [[package]] 1105 | name = "predicates" 1106 | version = "3.1.3" 1107 | source = "registry+https://github.com/rust-lang/crates.io-index" 1108 | checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573" 1109 | dependencies = [ 1110 | "anstyle", 1111 | "difflib", 1112 | "predicates-core", 1113 | ] 1114 | 1115 | [[package]] 1116 | name = "predicates-core" 1117 | version = "1.0.9" 1118 | source = "registry+https://github.com/rust-lang/crates.io-index" 1119 | checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa" 1120 | 1121 | [[package]] 1122 | name = "predicates-tree" 1123 | version = "1.0.12" 1124 | source = "registry+https://github.com/rust-lang/crates.io-index" 1125 | checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c" 1126 | dependencies = [ 1127 | "predicates-core", 1128 | "termtree", 1129 | ] 1130 | 1131 | [[package]] 1132 | name = "proc-macro-crate" 1133 | version = "3.4.0" 1134 | source = "registry+https://github.com/rust-lang/crates.io-index" 1135 | checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" 1136 | dependencies = [ 1137 | "toml_edit", 1138 | ] 1139 | 1140 | [[package]] 1141 | name = "proc-macro2" 1142 | version = "1.0.101" 1143 | source = "registry+https://github.com/rust-lang/crates.io-index" 1144 | checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" 1145 | dependencies = [ 1146 | "unicode-ident", 1147 | ] 1148 | 1149 | [[package]] 1150 | name = "quote" 1151 | version = "1.0.40" 1152 | source = "registry+https://github.com/rust-lang/crates.io-index" 1153 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 1154 | dependencies = [ 1155 | "proc-macro2", 1156 | ] 1157 | 1158 | [[package]] 1159 | name = "r-efi" 1160 | version = "5.3.0" 1161 | source = "registry+https://github.com/rust-lang/crates.io-index" 1162 | checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" 1163 | 1164 | [[package]] 1165 | name = "redox_syscall" 1166 | version = "0.5.17" 1167 | source = "registry+https://github.com/rust-lang/crates.io-index" 1168 | checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" 1169 | dependencies = [ 1170 | "bitflags 2.9.4", 1171 | ] 1172 | 1173 | [[package]] 1174 | name = "ref-cast" 1175 | version = "1.0.24" 1176 | source = "registry+https://github.com/rust-lang/crates.io-index" 1177 | checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" 1178 | dependencies = [ 1179 | "ref-cast-impl", 1180 | ] 1181 | 1182 | [[package]] 1183 | name = "ref-cast-impl" 1184 | version = "1.0.24" 1185 | source = "registry+https://github.com/rust-lang/crates.io-index" 1186 | checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" 1187 | dependencies = [ 1188 | "proc-macro2", 1189 | "quote", 1190 | "syn", 1191 | ] 1192 | 1193 | [[package]] 1194 | name = "regex" 1195 | version = "1.11.3" 1196 | source = "registry+https://github.com/rust-lang/crates.io-index" 1197 | checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c" 1198 | dependencies = [ 1199 | "aho-corasick", 1200 | "memchr", 1201 | "regex-automata", 1202 | "regex-syntax", 1203 | ] 1204 | 1205 | [[package]] 1206 | name = "regex-automata" 1207 | version = "0.4.11" 1208 | source = "registry+https://github.com/rust-lang/crates.io-index" 1209 | checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" 1210 | dependencies = [ 1211 | "aho-corasick", 1212 | "memchr", 1213 | "regex-syntax", 1214 | ] 1215 | 1216 | [[package]] 1217 | name = "regex-syntax" 1218 | version = "0.8.6" 1219 | source = "registry+https://github.com/rust-lang/crates.io-index" 1220 | checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" 1221 | 1222 | [[package]] 1223 | name = "relative-path" 1224 | version = "1.9.3" 1225 | source = "registry+https://github.com/rust-lang/crates.io-index" 1226 | checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" 1227 | 1228 | [[package]] 1229 | name = "rowan" 1230 | version = "0.16.1" 1231 | source = "registry+https://github.com/rust-lang/crates.io-index" 1232 | checksum = "417a3a9f582e349834051b8a10c8d71ca88da4211e4093528e36b9845f6b5f21" 1233 | dependencies = [ 1234 | "countme", 1235 | "hashbrown 0.14.5", 1236 | "rustc-hash", 1237 | "text-size", 1238 | ] 1239 | 1240 | [[package]] 1241 | name = "rstest" 1242 | version = "0.21.0" 1243 | source = "registry+https://github.com/rust-lang/crates.io-index" 1244 | checksum = "9afd55a67069d6e434a95161415f5beeada95a01c7b815508a82dcb0e1593682" 1245 | dependencies = [ 1246 | "futures", 1247 | "futures-timer", 1248 | "rstest_macros", 1249 | "rustc_version", 1250 | ] 1251 | 1252 | [[package]] 1253 | name = "rstest_macros" 1254 | version = "0.21.0" 1255 | source = "registry+https://github.com/rust-lang/crates.io-index" 1256 | checksum = "4165dfae59a39dd41d8dec720d3cbfbc71f69744efb480a3920f5d4e0cc6798d" 1257 | dependencies = [ 1258 | "cfg-if", 1259 | "glob", 1260 | "proc-macro-crate", 1261 | "proc-macro2", 1262 | "quote", 1263 | "regex", 1264 | "relative-path", 1265 | "rustc_version", 1266 | "syn", 1267 | "unicode-ident", 1268 | ] 1269 | 1270 | [[package]] 1271 | name = "rustc-hash" 1272 | version = "1.1.0" 1273 | source = "registry+https://github.com/rust-lang/crates.io-index" 1274 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 1275 | 1276 | [[package]] 1277 | name = "rustc_version" 1278 | version = "0.4.1" 1279 | source = "registry+https://github.com/rust-lang/crates.io-index" 1280 | checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" 1281 | dependencies = [ 1282 | "semver", 1283 | ] 1284 | 1285 | [[package]] 1286 | name = "rustix" 1287 | version = "1.1.2" 1288 | source = "registry+https://github.com/rust-lang/crates.io-index" 1289 | checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" 1290 | dependencies = [ 1291 | "bitflags 2.9.4", 1292 | "errno", 1293 | "libc", 1294 | "linux-raw-sys", 1295 | "windows-sys 0.61.0", 1296 | ] 1297 | 1298 | [[package]] 1299 | name = "ryu" 1300 | version = "1.0.20" 1301 | source = "registry+https://github.com/rust-lang/crates.io-index" 1302 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 1303 | 1304 | [[package]] 1305 | name = "same-file" 1306 | version = "1.0.6" 1307 | source = "registry+https://github.com/rust-lang/crates.io-index" 1308 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 1309 | dependencies = [ 1310 | "winapi-util", 1311 | ] 1312 | 1313 | [[package]] 1314 | name = "schemars" 1315 | version = "1.0.4" 1316 | source = "registry+https://github.com/rust-lang/crates.io-index" 1317 | checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" 1318 | dependencies = [ 1319 | "dyn-clone", 1320 | "ref-cast", 1321 | "schemars_derive", 1322 | "serde", 1323 | "serde_json", 1324 | "url", 1325 | ] 1326 | 1327 | [[package]] 1328 | name = "schemars_derive" 1329 | version = "1.0.4" 1330 | source = "registry+https://github.com/rust-lang/crates.io-index" 1331 | checksum = "33d020396d1d138dc19f1165df7545479dcd58d93810dc5d646a16e55abefa80" 1332 | dependencies = [ 1333 | "proc-macro2", 1334 | "quote", 1335 | "serde_derive_internals", 1336 | "syn", 1337 | ] 1338 | 1339 | [[package]] 1340 | name = "scopeguard" 1341 | version = "1.2.0" 1342 | source = "registry+https://github.com/rust-lang/crates.io-index" 1343 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1344 | 1345 | [[package]] 1346 | name = "semver" 1347 | version = "1.0.27" 1348 | source = "registry+https://github.com/rust-lang/crates.io-index" 1349 | checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" 1350 | dependencies = [ 1351 | "serde", 1352 | "serde_core", 1353 | ] 1354 | 1355 | [[package]] 1356 | name = "serde" 1357 | version = "1.0.226" 1358 | source = "registry+https://github.com/rust-lang/crates.io-index" 1359 | checksum = "0dca6411025b24b60bfa7ec1fe1f8e710ac09782dca409ee8237ba74b51295fd" 1360 | dependencies = [ 1361 | "serde_core", 1362 | "serde_derive", 1363 | ] 1364 | 1365 | [[package]] 1366 | name = "serde_core" 1367 | version = "1.0.226" 1368 | source = "registry+https://github.com/rust-lang/crates.io-index" 1369 | checksum = "ba2ba63999edb9dac981fb34b3e5c0d111a69b0924e253ed29d83f7c99e966a4" 1370 | dependencies = [ 1371 | "serde_derive", 1372 | ] 1373 | 1374 | [[package]] 1375 | name = "serde_derive" 1376 | version = "1.0.226" 1377 | source = "registry+https://github.com/rust-lang/crates.io-index" 1378 | checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33" 1379 | dependencies = [ 1380 | "proc-macro2", 1381 | "quote", 1382 | "syn", 1383 | ] 1384 | 1385 | [[package]] 1386 | name = "serde_derive_internals" 1387 | version = "0.29.1" 1388 | source = "registry+https://github.com/rust-lang/crates.io-index" 1389 | checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" 1390 | dependencies = [ 1391 | "proc-macro2", 1392 | "quote", 1393 | "syn", 1394 | ] 1395 | 1396 | [[package]] 1397 | name = "serde_json" 1398 | version = "1.0.145" 1399 | source = "registry+https://github.com/rust-lang/crates.io-index" 1400 | checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" 1401 | dependencies = [ 1402 | "indexmap 2.11.4", 1403 | "itoa", 1404 | "memchr", 1405 | "ryu", 1406 | "serde", 1407 | "serde_core", 1408 | ] 1409 | 1410 | [[package]] 1411 | name = "serde_json_bytes" 1412 | version = "0.2.5" 1413 | source = "registry+https://github.com/rust-lang/crates.io-index" 1414 | checksum = "a6a27c10711f94d1042b4c96d483556ec84371864e25d0e1cf3dc1024b0880b1" 1415 | dependencies = [ 1416 | "ahash", 1417 | "bytes", 1418 | "indexmap 2.11.4", 1419 | "jsonpath-rust", 1420 | "regex", 1421 | "serde", 1422 | "serde_json", 1423 | ] 1424 | 1425 | [[package]] 1426 | name = "serde_with" 1427 | version = "3.14.1" 1428 | source = "registry+https://github.com/rust-lang/crates.io-index" 1429 | checksum = "c522100790450cf78eeac1507263d0a350d4d5b30df0c8e1fe051a10c22b376e" 1430 | dependencies = [ 1431 | "serde", 1432 | "serde_derive", 1433 | "serde_with_macros", 1434 | ] 1435 | 1436 | [[package]] 1437 | name = "serde_with_macros" 1438 | version = "3.14.1" 1439 | source = "registry+https://github.com/rust-lang/crates.io-index" 1440 | checksum = "327ada00f7d64abaac1e55a6911e90cf665aa051b9a561c7006c157f4633135e" 1441 | dependencies = [ 1442 | "darling", 1443 | "proc-macro2", 1444 | "quote", 1445 | "syn", 1446 | ] 1447 | 1448 | [[package]] 1449 | name = "serde_yaml" 1450 | version = "0.8.26" 1451 | source = "registry+https://github.com/rust-lang/crates.io-index" 1452 | checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b" 1453 | dependencies = [ 1454 | "indexmap 1.9.3", 1455 | "ryu", 1456 | "serde", 1457 | "yaml-rust", 1458 | ] 1459 | 1460 | [[package]] 1461 | name = "sha2" 1462 | version = "0.10.9" 1463 | source = "registry+https://github.com/rust-lang/crates.io-index" 1464 | checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" 1465 | dependencies = [ 1466 | "cfg-if", 1467 | "cpufeatures", 1468 | "digest", 1469 | ] 1470 | 1471 | [[package]] 1472 | name = "shape" 1473 | version = "0.6.0" 1474 | source = "registry+https://github.com/rust-lang/crates.io-index" 1475 | checksum = "48f06e8e6e2486e2ca1fc86254acb38bca0cd7da30af443e8d63958c66738f88" 1476 | dependencies = [ 1477 | "apollo-compiler", 1478 | "indexmap 2.11.4", 1479 | "serde_json", 1480 | "serde_json_bytes", 1481 | ] 1482 | 1483 | [[package]] 1484 | name = "slab" 1485 | version = "0.4.11" 1486 | source = "registry+https://github.com/rust-lang/crates.io-index" 1487 | checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" 1488 | 1489 | [[package]] 1490 | name = "smallvec" 1491 | version = "1.15.1" 1492 | source = "registry+https://github.com/rust-lang/crates.io-index" 1493 | checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" 1494 | 1495 | [[package]] 1496 | name = "stable_deref_trait" 1497 | version = "1.2.0" 1498 | source = "registry+https://github.com/rust-lang/crates.io-index" 1499 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 1500 | 1501 | [[package]] 1502 | name = "strsim" 1503 | version = "0.11.1" 1504 | source = "registry+https://github.com/rust-lang/crates.io-index" 1505 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1506 | 1507 | [[package]] 1508 | name = "strum" 1509 | version = "0.27.2" 1510 | source = "registry+https://github.com/rust-lang/crates.io-index" 1511 | checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" 1512 | 1513 | [[package]] 1514 | name = "strum_macros" 1515 | version = "0.27.2" 1516 | source = "registry+https://github.com/rust-lang/crates.io-index" 1517 | checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" 1518 | dependencies = [ 1519 | "heck", 1520 | "proc-macro2", 1521 | "quote", 1522 | "syn", 1523 | ] 1524 | 1525 | [[package]] 1526 | name = "syn" 1527 | version = "2.0.106" 1528 | source = "registry+https://github.com/rust-lang/crates.io-index" 1529 | checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" 1530 | dependencies = [ 1531 | "proc-macro2", 1532 | "quote", 1533 | "unicode-ident", 1534 | ] 1535 | 1536 | [[package]] 1537 | name = "synstructure" 1538 | version = "0.13.2" 1539 | source = "registry+https://github.com/rust-lang/crates.io-index" 1540 | checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" 1541 | dependencies = [ 1542 | "proc-macro2", 1543 | "quote", 1544 | "syn", 1545 | ] 1546 | 1547 | [[package]] 1548 | name = "tempfile" 1549 | version = "3.23.0" 1550 | source = "registry+https://github.com/rust-lang/crates.io-index" 1551 | checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" 1552 | dependencies = [ 1553 | "fastrand", 1554 | "getrandom", 1555 | "once_cell", 1556 | "rustix", 1557 | "windows-sys 0.61.0", 1558 | ] 1559 | 1560 | [[package]] 1561 | name = "termtree" 1562 | version = "0.5.1" 1563 | source = "registry+https://github.com/rust-lang/crates.io-index" 1564 | checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" 1565 | 1566 | [[package]] 1567 | name = "text-size" 1568 | version = "1.1.1" 1569 | source = "registry+https://github.com/rust-lang/crates.io-index" 1570 | checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233" 1571 | 1572 | [[package]] 1573 | name = "thiserror" 1574 | version = "1.0.69" 1575 | source = "registry+https://github.com/rust-lang/crates.io-index" 1576 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 1577 | dependencies = [ 1578 | "thiserror-impl 1.0.69", 1579 | ] 1580 | 1581 | [[package]] 1582 | name = "thiserror" 1583 | version = "2.0.16" 1584 | source = "registry+https://github.com/rust-lang/crates.io-index" 1585 | checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" 1586 | dependencies = [ 1587 | "thiserror-impl 2.0.16", 1588 | ] 1589 | 1590 | [[package]] 1591 | name = "thiserror-impl" 1592 | version = "1.0.69" 1593 | source = "registry+https://github.com/rust-lang/crates.io-index" 1594 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 1595 | dependencies = [ 1596 | "proc-macro2", 1597 | "quote", 1598 | "syn", 1599 | ] 1600 | 1601 | [[package]] 1602 | name = "thiserror-impl" 1603 | version = "2.0.16" 1604 | source = "registry+https://github.com/rust-lang/crates.io-index" 1605 | checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" 1606 | dependencies = [ 1607 | "proc-macro2", 1608 | "quote", 1609 | "syn", 1610 | ] 1611 | 1612 | [[package]] 1613 | name = "time" 1614 | version = "0.3.44" 1615 | source = "registry+https://github.com/rust-lang/crates.io-index" 1616 | checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" 1617 | dependencies = [ 1618 | "deranged", 1619 | "libc", 1620 | "num-conv", 1621 | "num_threads", 1622 | "powerfmt", 1623 | "serde", 1624 | "time-core", 1625 | ] 1626 | 1627 | [[package]] 1628 | name = "time-core" 1629 | version = "0.1.6" 1630 | source = "registry+https://github.com/rust-lang/crates.io-index" 1631 | checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" 1632 | 1633 | [[package]] 1634 | name = "tinystr" 1635 | version = "0.8.1" 1636 | source = "registry+https://github.com/rust-lang/crates.io-index" 1637 | checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" 1638 | dependencies = [ 1639 | "displaydoc", 1640 | "zerovec", 1641 | ] 1642 | 1643 | [[package]] 1644 | name = "toml_datetime" 1645 | version = "0.7.2" 1646 | source = "registry+https://github.com/rust-lang/crates.io-index" 1647 | checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1" 1648 | dependencies = [ 1649 | "serde_core", 1650 | ] 1651 | 1652 | [[package]] 1653 | name = "toml_edit" 1654 | version = "0.23.6" 1655 | source = "registry+https://github.com/rust-lang/crates.io-index" 1656 | checksum = "f3effe7c0e86fdff4f69cdd2ccc1b96f933e24811c5441d44904e8683e27184b" 1657 | dependencies = [ 1658 | "indexmap 2.11.4", 1659 | "toml_datetime", 1660 | "toml_parser", 1661 | "winnow", 1662 | ] 1663 | 1664 | [[package]] 1665 | name = "toml_parser" 1666 | version = "1.0.3" 1667 | source = "registry+https://github.com/rust-lang/crates.io-index" 1668 | checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627" 1669 | dependencies = [ 1670 | "winnow", 1671 | ] 1672 | 1673 | [[package]] 1674 | name = "tracing" 1675 | version = "0.1.41" 1676 | source = "registry+https://github.com/rust-lang/crates.io-index" 1677 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 1678 | dependencies = [ 1679 | "pin-project-lite", 1680 | "tracing-attributes", 1681 | "tracing-core", 1682 | ] 1683 | 1684 | [[package]] 1685 | name = "tracing-attributes" 1686 | version = "0.1.30" 1687 | source = "registry+https://github.com/rust-lang/crates.io-index" 1688 | checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" 1689 | dependencies = [ 1690 | "proc-macro2", 1691 | "quote", 1692 | "syn", 1693 | ] 1694 | 1695 | [[package]] 1696 | name = "tracing-core" 1697 | version = "0.1.34" 1698 | source = "registry+https://github.com/rust-lang/crates.io-index" 1699 | checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" 1700 | dependencies = [ 1701 | "once_cell", 1702 | ] 1703 | 1704 | [[package]] 1705 | name = "triomphe" 1706 | version = "0.1.14" 1707 | source = "registry+https://github.com/rust-lang/crates.io-index" 1708 | checksum = "ef8f7726da4807b58ea5c96fdc122f80702030edc33b35aff9190a51148ccc85" 1709 | dependencies = [ 1710 | "serde", 1711 | "stable_deref_trait", 1712 | ] 1713 | 1714 | [[package]] 1715 | name = "typed-arena" 1716 | version = "2.0.2" 1717 | source = "registry+https://github.com/rust-lang/crates.io-index" 1718 | checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" 1719 | 1720 | [[package]] 1721 | name = "typenum" 1722 | version = "1.18.0" 1723 | source = "registry+https://github.com/rust-lang/crates.io-index" 1724 | checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" 1725 | 1726 | [[package]] 1727 | name = "ucd-trie" 1728 | version = "0.1.7" 1729 | source = "registry+https://github.com/rust-lang/crates.io-index" 1730 | checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" 1731 | 1732 | [[package]] 1733 | name = "unicode-ident" 1734 | version = "1.0.19" 1735 | source = "registry+https://github.com/rust-lang/crates.io-index" 1736 | checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" 1737 | 1738 | [[package]] 1739 | name = "unicode-segmentation" 1740 | version = "1.12.0" 1741 | source = "registry+https://github.com/rust-lang/crates.io-index" 1742 | checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" 1743 | 1744 | [[package]] 1745 | name = "unicode-width" 1746 | version = "0.1.14" 1747 | source = "registry+https://github.com/rust-lang/crates.io-index" 1748 | checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" 1749 | 1750 | [[package]] 1751 | name = "unicode-xid" 1752 | version = "0.2.6" 1753 | source = "registry+https://github.com/rust-lang/crates.io-index" 1754 | checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" 1755 | 1756 | [[package]] 1757 | name = "url" 1758 | version = "2.5.7" 1759 | source = "registry+https://github.com/rust-lang/crates.io-index" 1760 | checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" 1761 | dependencies = [ 1762 | "form_urlencoded", 1763 | "idna", 1764 | "percent-encoding", 1765 | "serde", 1766 | ] 1767 | 1768 | [[package]] 1769 | name = "utf8_iter" 1770 | version = "1.0.4" 1771 | source = "registry+https://github.com/rust-lang/crates.io-index" 1772 | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 1773 | 1774 | [[package]] 1775 | name = "version_check" 1776 | version = "0.9.5" 1777 | source = "registry+https://github.com/rust-lang/crates.io-index" 1778 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 1779 | 1780 | [[package]] 1781 | name = "walkdir" 1782 | version = "2.5.0" 1783 | source = "registry+https://github.com/rust-lang/crates.io-index" 1784 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 1785 | dependencies = [ 1786 | "same-file", 1787 | "winapi-util", 1788 | ] 1789 | 1790 | [[package]] 1791 | name = "wasi" 1792 | version = "0.14.7+wasi-0.2.4" 1793 | source = "registry+https://github.com/rust-lang/crates.io-index" 1794 | checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" 1795 | dependencies = [ 1796 | "wasip2", 1797 | ] 1798 | 1799 | [[package]] 1800 | name = "wasip2" 1801 | version = "1.0.1+wasi-0.2.4" 1802 | source = "registry+https://github.com/rust-lang/crates.io-index" 1803 | checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" 1804 | dependencies = [ 1805 | "wit-bindgen", 1806 | ] 1807 | 1808 | [[package]] 1809 | name = "winapi-util" 1810 | version = "0.1.11" 1811 | source = "registry+https://github.com/rust-lang/crates.io-index" 1812 | checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" 1813 | dependencies = [ 1814 | "windows-sys 0.61.0", 1815 | ] 1816 | 1817 | [[package]] 1818 | name = "windows-link" 1819 | version = "0.2.0" 1820 | source = "registry+https://github.com/rust-lang/crates.io-index" 1821 | checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" 1822 | 1823 | [[package]] 1824 | name = "windows-sys" 1825 | version = "0.45.0" 1826 | source = "registry+https://github.com/rust-lang/crates.io-index" 1827 | checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" 1828 | dependencies = [ 1829 | "windows-targets 0.42.2", 1830 | ] 1831 | 1832 | [[package]] 1833 | name = "windows-sys" 1834 | version = "0.59.0" 1835 | source = "registry+https://github.com/rust-lang/crates.io-index" 1836 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 1837 | dependencies = [ 1838 | "windows-targets 0.52.6", 1839 | ] 1840 | 1841 | [[package]] 1842 | name = "windows-sys" 1843 | version = "0.61.0" 1844 | source = "registry+https://github.com/rust-lang/crates.io-index" 1845 | checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa" 1846 | dependencies = [ 1847 | "windows-link", 1848 | ] 1849 | 1850 | [[package]] 1851 | name = "windows-targets" 1852 | version = "0.42.2" 1853 | source = "registry+https://github.com/rust-lang/crates.io-index" 1854 | checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" 1855 | dependencies = [ 1856 | "windows_aarch64_gnullvm 0.42.2", 1857 | "windows_aarch64_msvc 0.42.2", 1858 | "windows_i686_gnu 0.42.2", 1859 | "windows_i686_msvc 0.42.2", 1860 | "windows_x86_64_gnu 0.42.2", 1861 | "windows_x86_64_gnullvm 0.42.2", 1862 | "windows_x86_64_msvc 0.42.2", 1863 | ] 1864 | 1865 | [[package]] 1866 | name = "windows-targets" 1867 | version = "0.52.6" 1868 | source = "registry+https://github.com/rust-lang/crates.io-index" 1869 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1870 | dependencies = [ 1871 | "windows_aarch64_gnullvm 0.52.6", 1872 | "windows_aarch64_msvc 0.52.6", 1873 | "windows_i686_gnu 0.52.6", 1874 | "windows_i686_gnullvm", 1875 | "windows_i686_msvc 0.52.6", 1876 | "windows_x86_64_gnu 0.52.6", 1877 | "windows_x86_64_gnullvm 0.52.6", 1878 | "windows_x86_64_msvc 0.52.6", 1879 | ] 1880 | 1881 | [[package]] 1882 | name = "windows_aarch64_gnullvm" 1883 | version = "0.42.2" 1884 | source = "registry+https://github.com/rust-lang/crates.io-index" 1885 | checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" 1886 | 1887 | [[package]] 1888 | name = "windows_aarch64_gnullvm" 1889 | version = "0.52.6" 1890 | source = "registry+https://github.com/rust-lang/crates.io-index" 1891 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1892 | 1893 | [[package]] 1894 | name = "windows_aarch64_msvc" 1895 | version = "0.42.2" 1896 | source = "registry+https://github.com/rust-lang/crates.io-index" 1897 | checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" 1898 | 1899 | [[package]] 1900 | name = "windows_aarch64_msvc" 1901 | version = "0.52.6" 1902 | source = "registry+https://github.com/rust-lang/crates.io-index" 1903 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1904 | 1905 | [[package]] 1906 | name = "windows_i686_gnu" 1907 | version = "0.42.2" 1908 | source = "registry+https://github.com/rust-lang/crates.io-index" 1909 | checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" 1910 | 1911 | [[package]] 1912 | name = "windows_i686_gnu" 1913 | version = "0.52.6" 1914 | source = "registry+https://github.com/rust-lang/crates.io-index" 1915 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1916 | 1917 | [[package]] 1918 | name = "windows_i686_gnullvm" 1919 | version = "0.52.6" 1920 | source = "registry+https://github.com/rust-lang/crates.io-index" 1921 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1922 | 1923 | [[package]] 1924 | name = "windows_i686_msvc" 1925 | version = "0.42.2" 1926 | source = "registry+https://github.com/rust-lang/crates.io-index" 1927 | checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" 1928 | 1929 | [[package]] 1930 | name = "windows_i686_msvc" 1931 | version = "0.52.6" 1932 | source = "registry+https://github.com/rust-lang/crates.io-index" 1933 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1934 | 1935 | [[package]] 1936 | name = "windows_x86_64_gnu" 1937 | version = "0.42.2" 1938 | source = "registry+https://github.com/rust-lang/crates.io-index" 1939 | checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" 1940 | 1941 | [[package]] 1942 | name = "windows_x86_64_gnu" 1943 | version = "0.52.6" 1944 | source = "registry+https://github.com/rust-lang/crates.io-index" 1945 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1946 | 1947 | [[package]] 1948 | name = "windows_x86_64_gnullvm" 1949 | version = "0.42.2" 1950 | source = "registry+https://github.com/rust-lang/crates.io-index" 1951 | checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" 1952 | 1953 | [[package]] 1954 | name = "windows_x86_64_gnullvm" 1955 | version = "0.52.6" 1956 | source = "registry+https://github.com/rust-lang/crates.io-index" 1957 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1958 | 1959 | [[package]] 1960 | name = "windows_x86_64_msvc" 1961 | version = "0.42.2" 1962 | source = "registry+https://github.com/rust-lang/crates.io-index" 1963 | checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" 1964 | 1965 | [[package]] 1966 | name = "windows_x86_64_msvc" 1967 | version = "0.52.6" 1968 | source = "registry+https://github.com/rust-lang/crates.io-index" 1969 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1970 | 1971 | [[package]] 1972 | name = "winnow" 1973 | version = "0.7.13" 1974 | source = "registry+https://github.com/rust-lang/crates.io-index" 1975 | checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" 1976 | dependencies = [ 1977 | "memchr", 1978 | ] 1979 | 1980 | [[package]] 1981 | name = "wit-bindgen" 1982 | version = "0.46.0" 1983 | source = "registry+https://github.com/rust-lang/crates.io-index" 1984 | checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" 1985 | 1986 | [[package]] 1987 | name = "writeable" 1988 | version = "0.6.1" 1989 | source = "registry+https://github.com/rust-lang/crates.io-index" 1990 | checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" 1991 | 1992 | [[package]] 1993 | name = "yaml-rust" 1994 | version = "0.4.5" 1995 | source = "registry+https://github.com/rust-lang/crates.io-index" 1996 | checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" 1997 | dependencies = [ 1998 | "linked-hash-map", 1999 | ] 2000 | 2001 | [[package]] 2002 | name = "yansi" 2003 | version = "1.0.1" 2004 | source = "registry+https://github.com/rust-lang/crates.io-index" 2005 | checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" 2006 | 2007 | [[package]] 2008 | name = "yoke" 2009 | version = "0.8.0" 2010 | source = "registry+https://github.com/rust-lang/crates.io-index" 2011 | checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" 2012 | dependencies = [ 2013 | "serde", 2014 | "stable_deref_trait", 2015 | "yoke-derive", 2016 | "zerofrom", 2017 | ] 2018 | 2019 | [[package]] 2020 | name = "yoke-derive" 2021 | version = "0.8.0" 2022 | source = "registry+https://github.com/rust-lang/crates.io-index" 2023 | checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" 2024 | dependencies = [ 2025 | "proc-macro2", 2026 | "quote", 2027 | "syn", 2028 | "synstructure", 2029 | ] 2030 | 2031 | [[package]] 2032 | name = "zerocopy" 2033 | version = "0.8.27" 2034 | source = "registry+https://github.com/rust-lang/crates.io-index" 2035 | checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" 2036 | dependencies = [ 2037 | "zerocopy-derive", 2038 | ] 2039 | 2040 | [[package]] 2041 | name = "zerocopy-derive" 2042 | version = "0.8.27" 2043 | source = "registry+https://github.com/rust-lang/crates.io-index" 2044 | checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" 2045 | dependencies = [ 2046 | "proc-macro2", 2047 | "quote", 2048 | "syn", 2049 | ] 2050 | 2051 | [[package]] 2052 | name = "zerofrom" 2053 | version = "0.1.6" 2054 | source = "registry+https://github.com/rust-lang/crates.io-index" 2055 | checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" 2056 | dependencies = [ 2057 | "zerofrom-derive", 2058 | ] 2059 | 2060 | [[package]] 2061 | name = "zerofrom-derive" 2062 | version = "0.1.6" 2063 | source = "registry+https://github.com/rust-lang/crates.io-index" 2064 | checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" 2065 | dependencies = [ 2066 | "proc-macro2", 2067 | "quote", 2068 | "syn", 2069 | "synstructure", 2070 | ] 2071 | 2072 | [[package]] 2073 | name = "zerotrie" 2074 | version = "0.2.2" 2075 | source = "registry+https://github.com/rust-lang/crates.io-index" 2076 | checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" 2077 | dependencies = [ 2078 | "displaydoc", 2079 | "yoke", 2080 | "zerofrom", 2081 | ] 2082 | 2083 | [[package]] 2084 | name = "zerovec" 2085 | version = "0.11.4" 2086 | source = "registry+https://github.com/rust-lang/crates.io-index" 2087 | checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" 2088 | dependencies = [ 2089 | "yoke", 2090 | "zerofrom", 2091 | "zerovec-derive", 2092 | ] 2093 | 2094 | [[package]] 2095 | name = "zerovec-derive" 2096 | version = "0.11.1" 2097 | source = "registry+https://github.com/rust-lang/crates.io-index" 2098 | checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" 2099 | dependencies = [ 2100 | "proc-macro2", 2101 | "quote", 2102 | "syn", 2103 | ] 2104 | --------------------------------------------------------------------------------