Skip to content

Commit

Permalink
Add JSONSchema generation support for supergraph.yaml files (#582)
Browse files Browse the repository at this point in the history
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`.
  • Loading branch information
dylan-apollo authored Nov 4, 2024
1 parent 3075be5 commit ef4c112
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 31 deletions.
53 changes: 43 additions & 10 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion apollo-federation-types/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<Vec<SubgraphASTNode>>`
- 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
Expand Down
5 changes: 3 additions & 2 deletions apollo-federation-types/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
6 changes: 4 additions & 2 deletions apollo-federation-types/src/config/subgraph.rs
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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,
Expand Down
20 changes: 8 additions & 12 deletions apollo-federation-types/src/config/supergraph.rs
Original file line number Diff line number Diff line change
@@ -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::{
Expand All @@ -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<String, SubgraphConfig>,
Expand Down Expand Up @@ -55,13 +55,11 @@ impl SupergraphConfig {
}

/// Create a new SupergraphConfig from a YAML file.
pub fn new_from_yaml_file<P: Into<Utf8PathBuf>>(
config_path: P,
) -> ConfigResult<SupergraphConfig> {
let config_path: Utf8PathBuf = config_path.into();
pub fn new_from_yaml_file<P: Into<PathBuf>>(config_path: P) -> ConfigResult<SupergraphConfig> {
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(),
})?;

Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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();

Expand Down
25 changes: 21 additions & 4 deletions apollo-federation-types/src/config/version.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -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<D>(deserializer: D) -> Result<Self, D::Error>
where
Expand Down

0 comments on commit ef4c112

Please sign in to comment.