From ef4c112a1dbdfd09d0dfc581bb41c2699c5d5fae Mon Sep 17 00:00:00 2001 From: Dylan Anthony Date: Mon, 4 Nov 2024 10:15:31 -0700 Subject: [PATCH] Add JSONSchema generation support for `supergraph.yaml` files (#582) This was done as a one-off for our extension (which will soon include validation for `supergraph.yaml` files!), but I figure we should keep it around so we can regenerate again in the future. We can/should also add a command in `rover` to do this, similar to the equivalent of `router`, so folks can generate schemas for their IDEs of choice. Removes `camino` because it is not supported by `schemars`. --- Cargo.lock | 53 +++++++++++++++---- apollo-federation-types/CHANGELOG.md | 8 ++- apollo-federation-types/Cargo.toml | 5 +- .../src/config/subgraph.rs | 6 ++- .../src/config/supergraph.rs | 20 +++---- apollo-federation-types/src/config/version.rs | 25 +++++++-- 6 files changed, 86 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 22ddc7561..228777b7d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -120,9 +120,9 @@ name = "apollo-federation-types" version = "0.14.1" dependencies = [ "assert_fs", - "camino", "log", "rstest", + "schemars", "semver 1.0.17", "serde", "serde_json", @@ -288,15 +288,6 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" -[[package]] -name = "camino" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c530edf18f37068ac2d977409ed5cd50d53d73bc653c7647b48eb78976ac9ae2" -dependencies = [ - "serde", -] - [[package]] name = "cast" version = "0.3.0" @@ -751,6 +742,12 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +[[package]] +name = "dyn-clone" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" + [[package]] name = "either" version = "1.12.0" @@ -1972,6 +1969,31 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schemars" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", + "url", +] + +[[package]] +name = "schemars_derive" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.48", +] + [[package]] name = "scopeguard" version = "1.1.0" @@ -2031,6 +2053,17 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "serde_json" version = "1.0.117" diff --git a/apollo-federation-types/CHANGELOG.md b/apollo-federation-types/CHANGELOG.md index 9ceb48b1f..caab3d65a 100644 --- a/apollo-federation-types/CHANGELOG.md +++ b/apollo-federation-types/CHANGELOG.md @@ -2,15 +2,21 @@ Not every version is listed here because versions before 0.14.0 did not have a changelog. -## 0.15.0 (Unreleased) +## 0.15.0 - Unreleased ### Breaking changes - `GraphQLError.nodes` is now an `Option>` +- All usages of `camino::Utf8PathBuf` have been replaced with `std::path::PathBuf` + +### Features + +- A new `json_schema` feature derives the `schemars::JsonSchema` trait on `SupergraphConfig` and its sub-types. ## 0.14.1 - 2024-09-19 ### Features + - `impl FromIterator<(String, SubgraphConfig)> for SupergraphConfig` ## 0.14.0 - 2024-09-11 diff --git a/apollo-federation-types/Cargo.toml b/apollo-federation-types/Cargo.toml index 434195785..22e0179cb 100644 --- a/apollo-federation-types/Cargo.toml +++ b/apollo-federation-types/Cargo.toml @@ -15,14 +15,15 @@ default = ["config", "build", "build_plugin"] build = ["serde_json"] build_plugin = ["serde_json"] -config = ["camino", "log", "thiserror", "serde_yaml", "url", "serde_with"] +config = ["log", "thiserror", "serde_yaml", "url", "serde_with"] +json_schema = ["schemars"] [dependencies] # config and build dependencies serde = { version = "1", features = ["derive"] } +schemars = { version = "0.8.21", optional = true, features = ["url"]} # config-only dependencies -camino = { version = "1", features = ["serde1"], optional = true } log = { version = "0.4", optional = true } semver = { version = "1", features = ["serde"] } serde_with = { version = "3", default-features = false, features = ["macros"], optional = true } diff --git a/apollo-federation-types/src/config/subgraph.rs b/apollo-federation-types/src/config/subgraph.rs index 91b7ee397..0ac369092 100644 --- a/apollo-federation-types/src/config/subgraph.rs +++ b/apollo-federation-types/src/config/subgraph.rs @@ -1,10 +1,11 @@ -use camino::Utf8PathBuf; use serde::{Deserialize, Serialize}; use std::collections::HashMap; +use std::path::PathBuf; use url::Url; /// Config for a single [subgraph](https://www.apollographql.com/docs/federation/subgraphs/) #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))] pub struct SubgraphConfig { /// The routing URL for the subgraph. /// This will appear in supergraph SDL and @@ -37,10 +38,11 @@ impl SubgraphConfig { // this is untagged, meaning its fields will be flattened into the parent // struct when de/serialized. There is no top level `schema_source` // in the configuration. +#[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))] #[serde(untagged)] pub enum SchemaSource { File { - file: Utf8PathBuf, + file: PathBuf, }, SubgraphIntrospection { subgraph_url: Url, diff --git a/apollo-federation-types/src/config/supergraph.rs b/apollo-federation-types/src/config/supergraph.rs index 28bbbd213..7c35aa95c 100644 --- a/apollo-federation-types/src/config/supergraph.rs +++ b/apollo-federation-types/src/config/supergraph.rs @@ -1,6 +1,5 @@ -use std::{collections::BTreeMap, fs}; +use std::{collections::BTreeMap, fs, path::PathBuf}; -use camino::Utf8PathBuf; use serde::{Deserialize, Serialize}; use crate::{ @@ -11,6 +10,7 @@ use crate::{ /// The configuration for a single supergraph /// composed of multiple subgraphs. #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))] pub struct SupergraphConfig { // Store config in a BTreeMap, as HashMap is non-deterministic. subgraphs: BTreeMap, @@ -55,13 +55,11 @@ impl SupergraphConfig { } /// Create a new SupergraphConfig from a YAML file. - pub fn new_from_yaml_file>( - config_path: P, - ) -> ConfigResult { - let config_path: Utf8PathBuf = config_path.into(); + pub fn new_from_yaml_file>(config_path: P) -> ConfigResult { + let config_path: PathBuf = config_path.into(); let supergraph_yaml = fs::read_to_string(&config_path).map_err(|e| ConfigError::MissingFile { - file_path: config_path.to_string(), + file_path: config_path.display().to_string(), message: e.to_string(), })?; @@ -177,15 +175,13 @@ impl FromIterator<(String, SubgraphConfig)> for SupergraphConfig { #[cfg(test)] mod tests { - use std::{collections::BTreeMap, convert::TryFrom, fs}; + use std::{collections::BTreeMap, convert::TryFrom, fs, path::PathBuf}; use assert_fs::TempDir; - use camino::Utf8PathBuf; use semver::Version; - use crate::config::{FederationVersion, SchemaSource, SubgraphConfig}; - use super::SupergraphConfig; + use crate::config::{FederationVersion, SchemaSource, SubgraphConfig}; #[test] fn it_can_parse_valid_config_without_version() { @@ -513,7 +509,7 @@ subgraphs: "#; let tmp_home = TempDir::new().unwrap(); - let mut config_path = Utf8PathBuf::try_from(tmp_home.path().to_path_buf()).unwrap(); + let mut config_path = PathBuf::try_from(tmp_home.path().to_path_buf()).unwrap(); config_path.push("config.yaml"); fs::write(&config_path, raw_good_yaml).unwrap(); diff --git a/apollo-federation-types/src/config/version.rs b/apollo-federation-types/src/config/version.rs index ff21d8bdd..74231d200 100644 --- a/apollo-federation-types/src/config/version.rs +++ b/apollo-federation-types/src/config/version.rs @@ -1,12 +1,16 @@ -use std::{ - fmt::{self, Display}, - str::FromStr, +#[cfg(feature = "json_schema")] +use schemars::{ + gen::SchemaGenerator, + schema::{Schema, SchemaObject}, }; - use semver::Version; use serde::de::Error; use serde::{Deserialize, Deserializer}; use serde_with::{DeserializeFromStr, SerializeDisplay}; +use std::{ + fmt::{self, Display}, + str::FromStr, +}; use crate::config::ConfigError; @@ -200,6 +204,19 @@ impl FromStr for FederationVersion { } } +#[cfg(feature = "json_schema")] +impl schemars::JsonSchema for FederationVersion { + fn schema_name() -> String { + String::from("FederationVersion") + } + + fn json_schema(_gen: &mut SchemaGenerator) -> Schema { + let mut schema = SchemaObject::default(); + schema.string().pattern = Some(r#"^(1|2|=2\.\d+\.\d+.*)$"#.to_string()); + schema.into() + } +} + impl<'de> Deserialize<'de> for FederationVersion { fn deserialize(deserializer: D) -> Result where