├── .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 |
--------------------------------------------------------------------------------