Add Schema::sdl
method. #191
This commit is contained in:
parent
6947d95030
commit
312b8f13d8
204
src/registry/export_sdl.rs
Normal file
204
src/registry/export_sdl.rs
Normal file
|
@ -0,0 +1,204 @@
|
|||
use crate::registry::{MetaField, MetaInputValue, MetaType, Registry};
|
||||
use itertools::Itertools;
|
||||
use std::fmt::Write;
|
||||
|
||||
impl Registry {
|
||||
pub fn export_sdl(&self, federation: bool) -> String {
|
||||
let mut sdl = String::new();
|
||||
|
||||
for ty in self.types.values() {
|
||||
if ty.name().starts_with("__") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if federation {
|
||||
const FEDERATION_TYPES: &[&str] = &["_Any", "_Entity", "_Service"];
|
||||
if FEDERATION_TYPES.contains(&ty.name()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
self.export_type(ty, &mut sdl, federation);
|
||||
}
|
||||
|
||||
if !federation {
|
||||
writeln!(sdl, "schema {{").ok();
|
||||
writeln!(sdl, "\tquery: {}", self.query_type).ok();
|
||||
if let Some(mutation_type) = self.mutation_type.as_deref() {
|
||||
writeln!(sdl, "\tmutation: {}", mutation_type).ok();
|
||||
}
|
||||
if let Some(subscription_type) = self.subscription_type.as_deref() {
|
||||
writeln!(sdl, "\tsubscription: {}", subscription_type).ok();
|
||||
}
|
||||
writeln!(sdl, "}}").ok();
|
||||
}
|
||||
|
||||
sdl
|
||||
}
|
||||
|
||||
fn export_fields<'a, I: Iterator<Item = &'a MetaField>>(
|
||||
sdl: &mut String,
|
||||
it: I,
|
||||
federation: bool,
|
||||
) {
|
||||
for field in it {
|
||||
if field.name.starts_with("__")
|
||||
|| (federation && matches!(&*field.name, "_service" | "_entities"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if !field.args.is_empty() {
|
||||
write!(
|
||||
sdl,
|
||||
"\t{}({}): {}",
|
||||
field.name,
|
||||
field
|
||||
.args
|
||||
.values()
|
||||
.map(|arg| export_input_value(arg))
|
||||
.join(", "),
|
||||
field.ty
|
||||
)
|
||||
.ok();
|
||||
} else {
|
||||
write!(sdl, "\t{}: {}", field.name, field.ty).ok();
|
||||
}
|
||||
|
||||
if federation {
|
||||
if field.external {
|
||||
write!(sdl, " @external").ok();
|
||||
}
|
||||
if let Some(requires) = field.requires {
|
||||
write!(sdl, " @requires(fields: \"{}\")", requires).ok();
|
||||
}
|
||||
if let Some(provides) = field.provides {
|
||||
write!(sdl, " @provides(fields: \"{}\")", provides).ok();
|
||||
}
|
||||
}
|
||||
|
||||
writeln!(sdl).ok();
|
||||
}
|
||||
}
|
||||
|
||||
fn export_type(&self, ty: &MetaType, sdl: &mut String, federation: bool) {
|
||||
match ty {
|
||||
MetaType::Scalar { name, .. } => {
|
||||
const SYSTEM_SCALARS: &[&str] = &["Int", "Float", "String", "Boolean", "ID"];
|
||||
const FEDERATION_SCALARS: &[&str] = &["Any"];
|
||||
let mut export_scalar = !SYSTEM_SCALARS.contains(&name.as_str());
|
||||
if federation && FEDERATION_SCALARS.contains(&name.as_str()) {
|
||||
export_scalar = false;
|
||||
}
|
||||
if export_scalar {
|
||||
writeln!(sdl, "scalar {}", name).ok();
|
||||
}
|
||||
}
|
||||
MetaType::Object {
|
||||
name,
|
||||
fields,
|
||||
extends,
|
||||
keys,
|
||||
..
|
||||
} => {
|
||||
if name == &self.query_type && self.is_empty_query {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(subscription_type) = &self.subscription_type {
|
||||
if name == subscription_type && federation {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if federation && *extends {
|
||||
write!(sdl, "extend ").ok();
|
||||
}
|
||||
write!(sdl, "type {} ", name).ok();
|
||||
if let Some(implements) = self.implements.get(name) {
|
||||
if !implements.is_empty() {
|
||||
write!(sdl, "implements {} ", implements.iter().join(" & ")).ok();
|
||||
}
|
||||
}
|
||||
|
||||
if federation {
|
||||
if let Some(keys) = keys {
|
||||
for key in keys {
|
||||
write!(sdl, "@key(fields: \"{}\") ", key).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
writeln!(sdl, "{{").ok();
|
||||
Self::export_fields(sdl, fields.values(), federation);
|
||||
writeln!(sdl, "}}").ok();
|
||||
}
|
||||
MetaType::Interface {
|
||||
name,
|
||||
fields,
|
||||
extends,
|
||||
keys,
|
||||
..
|
||||
} => {
|
||||
if federation && *extends {
|
||||
write!(sdl, "extend ").ok();
|
||||
}
|
||||
write!(sdl, "interface {} ", name).ok();
|
||||
if federation {
|
||||
if let Some(keys) = keys {
|
||||
for key in keys {
|
||||
write!(sdl, "@key(fields: \"{}\") ", key).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
writeln!(sdl, "{{").ok();
|
||||
Self::export_fields(sdl, fields.values(), federation);
|
||||
writeln!(sdl, "}}").ok();
|
||||
}
|
||||
MetaType::Enum {
|
||||
name, enum_values, ..
|
||||
} => {
|
||||
write!(sdl, "enum {} ", name).ok();
|
||||
writeln!(sdl, "{{").ok();
|
||||
for value in enum_values.values() {
|
||||
writeln!(sdl, "\t{}", value.name).ok();
|
||||
}
|
||||
writeln!(sdl, "}}").ok();
|
||||
}
|
||||
MetaType::InputObject {
|
||||
name, input_fields, ..
|
||||
} => {
|
||||
write!(sdl, "input {} ", name).ok();
|
||||
writeln!(sdl, "{{").ok();
|
||||
for field in input_fields.values() {
|
||||
writeln!(sdl, "{}", export_input_value(&field)).ok();
|
||||
}
|
||||
writeln!(sdl, "}}").ok();
|
||||
}
|
||||
MetaType::Union {
|
||||
name,
|
||||
possible_types,
|
||||
..
|
||||
} => {
|
||||
writeln!(
|
||||
sdl,
|
||||
"union {} = {}",
|
||||
name,
|
||||
possible_types.iter().join(" | ")
|
||||
)
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn export_input_value(input_value: &MetaInputValue) -> String {
|
||||
if let Some(default_value) = &input_value.default_value {
|
||||
format!(
|
||||
"{}: {} = {}",
|
||||
input_value.name, input_value.ty, default_value
|
||||
)
|
||||
} else {
|
||||
format!("{}: {}", input_value.name, input_value.ty)
|
||||
}
|
||||
}
|
|
@ -1,248 +0,0 @@
|
|||
use crate::registry::{MetaField, MetaInputValue, MetaType, Registry};
|
||||
use crate::{Any, Type};
|
||||
use indexmap::IndexMap;
|
||||
use itertools::Itertools;
|
||||
use std::fmt::Write;
|
||||
|
||||
impl Registry {
|
||||
pub fn create_federation_sdl(&self) -> String {
|
||||
let mut sdl = String::new();
|
||||
for ty in self.types.values() {
|
||||
if ty.name().starts_with("__") {
|
||||
continue;
|
||||
}
|
||||
const FEDERATION_TYPES: &[&str] = &["_Any", "_Entity", "_Service"];
|
||||
if FEDERATION_TYPES.contains(&ty.name()) {
|
||||
continue;
|
||||
}
|
||||
self.create_federation_type(ty, &mut sdl);
|
||||
}
|
||||
sdl
|
||||
}
|
||||
|
||||
pub fn create_federation_types(&mut self) {
|
||||
Any::create_type_info(self);
|
||||
|
||||
self.types.insert(
|
||||
"_Service".to_string(),
|
||||
MetaType::Object {
|
||||
name: "_Service".to_string(),
|
||||
description: None,
|
||||
fields: {
|
||||
let mut fields = IndexMap::new();
|
||||
fields.insert(
|
||||
"sdl".to_string(),
|
||||
MetaField {
|
||||
name: "sdl".to_string(),
|
||||
description: None,
|
||||
args: Default::default(),
|
||||
ty: "String".to_string(),
|
||||
deprecation: None,
|
||||
cache_control: Default::default(),
|
||||
external: false,
|
||||
requires: None,
|
||||
provides: None,
|
||||
},
|
||||
);
|
||||
fields
|
||||
},
|
||||
cache_control: Default::default(),
|
||||
extends: false,
|
||||
keys: None,
|
||||
},
|
||||
);
|
||||
|
||||
self.create_entity_type();
|
||||
|
||||
let query_root = self.types.get_mut(&self.query_type).unwrap();
|
||||
if let MetaType::Object { fields, .. } = query_root {
|
||||
fields.insert(
|
||||
"_service".to_string(),
|
||||
MetaField {
|
||||
name: "_service".to_string(),
|
||||
description: None,
|
||||
args: Default::default(),
|
||||
ty: "_Service!".to_string(),
|
||||
deprecation: None,
|
||||
cache_control: Default::default(),
|
||||
external: false,
|
||||
requires: None,
|
||||
provides: None,
|
||||
},
|
||||
);
|
||||
|
||||
fields.insert(
|
||||
"_entities".to_string(),
|
||||
MetaField {
|
||||
name: "_entities".to_string(),
|
||||
description: None,
|
||||
args: {
|
||||
let mut args = IndexMap::new();
|
||||
args.insert(
|
||||
"representations",
|
||||
MetaInputValue {
|
||||
name: "representations",
|
||||
description: None,
|
||||
ty: "[_Any!]!".to_string(),
|
||||
default_value: None,
|
||||
validator: None,
|
||||
},
|
||||
);
|
||||
args
|
||||
},
|
||||
ty: "[_Entity]!".to_string(),
|
||||
deprecation: None,
|
||||
cache_control: Default::default(),
|
||||
external: false,
|
||||
requires: None,
|
||||
provides: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn create_federation_fields<'a, I: Iterator<Item = &'a MetaField>>(sdl: &mut String, it: I) {
|
||||
for field in it {
|
||||
if field.name.starts_with("__") || matches!(&*field.name, "_service" | "_entities") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if !field.args.is_empty() {
|
||||
write!(
|
||||
sdl,
|
||||
"\t{}({}): {}",
|
||||
field.name,
|
||||
field
|
||||
.args
|
||||
.values()
|
||||
.map(|arg| federation_input_value(arg))
|
||||
.join(", "),
|
||||
field.ty
|
||||
)
|
||||
.ok();
|
||||
} else {
|
||||
write!(sdl, "\t{}: {}", field.name, field.ty).ok();
|
||||
}
|
||||
|
||||
if field.external {
|
||||
write!(sdl, " @external").ok();
|
||||
}
|
||||
if let Some(requires) = field.requires {
|
||||
write!(sdl, " @requires(fields: \"{}\")", requires).ok();
|
||||
}
|
||||
if let Some(provides) = field.provides {
|
||||
write!(sdl, " @provides(fields: \"{}\")", provides).ok();
|
||||
}
|
||||
writeln!(sdl).ok();
|
||||
}
|
||||
}
|
||||
|
||||
fn create_federation_type(&self, ty: &MetaType, sdl: &mut String) {
|
||||
match ty {
|
||||
MetaType::Scalar { name, .. } => {
|
||||
const SYSTEM_SCALARS: &[&str] = &["Int", "Float", "String", "Boolean", "ID", "Any"];
|
||||
if !SYSTEM_SCALARS.contains(&name.as_str()) {
|
||||
writeln!(sdl, "scalar {}", name).ok();
|
||||
}
|
||||
}
|
||||
MetaType::Object {
|
||||
name,
|
||||
fields,
|
||||
extends,
|
||||
keys,
|
||||
..
|
||||
} => {
|
||||
if name == &self.query_type && fields.len() == 4 {
|
||||
// Is empty query root, only __schema, __type, _service, _entities fields
|
||||
return;
|
||||
}
|
||||
if let Some(subscription_type) = &self.subscription_type {
|
||||
if name == subscription_type {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if *extends {
|
||||
write!(sdl, "extend ").ok();
|
||||
}
|
||||
write!(sdl, "type {} ", name).ok();
|
||||
if let Some(implements) = self.implements.get(name) {
|
||||
if !implements.is_empty() {
|
||||
write!(sdl, "implements {}", implements.iter().join(" & ")).ok();
|
||||
}
|
||||
}
|
||||
if let Some(keys) = keys {
|
||||
for key in keys {
|
||||
write!(sdl, "@key(fields: \"{}\") ", key).ok();
|
||||
}
|
||||
}
|
||||
writeln!(sdl, "{{").ok();
|
||||
Self::create_federation_fields(sdl, fields.values());
|
||||
writeln!(sdl, "}}").ok();
|
||||
}
|
||||
MetaType::Interface {
|
||||
name,
|
||||
fields,
|
||||
extends,
|
||||
keys,
|
||||
..
|
||||
} => {
|
||||
if *extends {
|
||||
write!(sdl, "extend ").ok();
|
||||
}
|
||||
write!(sdl, "interface {} ", name).ok();
|
||||
if let Some(keys) = keys {
|
||||
for key in keys {
|
||||
write!(sdl, "@key(fields: \"{}\") ", key).ok();
|
||||
}
|
||||
}
|
||||
writeln!(sdl, "{{").ok();
|
||||
Self::create_federation_fields(sdl, fields.values());
|
||||
writeln!(sdl, "}}").ok();
|
||||
}
|
||||
MetaType::Enum {
|
||||
name, enum_values, ..
|
||||
} => {
|
||||
write!(sdl, "enum {} ", name).ok();
|
||||
writeln!(sdl, "{{").ok();
|
||||
for value in enum_values.values() {
|
||||
writeln!(sdl, "{}", value.name).ok();
|
||||
}
|
||||
writeln!(sdl, "}}").ok();
|
||||
}
|
||||
MetaType::InputObject {
|
||||
name, input_fields, ..
|
||||
} => {
|
||||
write!(sdl, "input {} ", name).ok();
|
||||
writeln!(sdl, "{{").ok();
|
||||
for field in input_fields.values() {
|
||||
writeln!(sdl, "{}", federation_input_value(&field)).ok();
|
||||
}
|
||||
writeln!(sdl, "}}").ok();
|
||||
}
|
||||
MetaType::Union {
|
||||
name,
|
||||
possible_types,
|
||||
..
|
||||
} => {
|
||||
writeln!(
|
||||
sdl,
|
||||
"union {} = {}",
|
||||
name,
|
||||
possible_types.iter().join(" | ")
|
||||
)
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn federation_input_value(input_value: &MetaInputValue) -> String {
|
||||
if let Some(default_value) = &input_value.default_value {
|
||||
format!(
|
||||
"{}: {} = {}",
|
||||
input_value.name, input_value.ty, default_value
|
||||
)
|
||||
} else {
|
||||
format!("{}: {}", input_value.name, input_value.ty)
|
||||
}
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
mod cache_control;
|
||||
mod federation;
|
||||
mod export_sdl;
|
||||
|
||||
use crate::parser::types::{BaseType as ParsedBaseType, Type as ParsedType};
|
||||
use crate::validators::InputValueValidator;
|
||||
use crate::{model, Value};
|
||||
use crate::{model, Any, Type, Value};
|
||||
use indexmap::map::IndexMap;
|
||||
use indexmap::set::IndexSet;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
@ -253,10 +253,11 @@ pub struct MetaDirective {
|
|||
}
|
||||
|
||||
pub struct Registry {
|
||||
pub types: HashMap<String, MetaType>,
|
||||
pub types: IndexMap<String, MetaType>,
|
||||
pub directives: HashMap<String, MetaDirective>,
|
||||
pub implements: HashMap<String, HashSet<String>>,
|
||||
pub query_type: String,
|
||||
pub is_empty_query: bool,
|
||||
pub mutation_type: Option<String>,
|
||||
pub subscription_type: Option<String>,
|
||||
}
|
||||
|
@ -368,4 +369,85 @@ impl Registry {
|
|||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub(crate) fn create_federation_types(&mut self) {
|
||||
Any::create_type_info(self);
|
||||
|
||||
self.types.insert(
|
||||
"_Service".to_string(),
|
||||
MetaType::Object {
|
||||
name: "_Service".to_string(),
|
||||
description: None,
|
||||
fields: {
|
||||
let mut fields = IndexMap::new();
|
||||
fields.insert(
|
||||
"sdl".to_string(),
|
||||
MetaField {
|
||||
name: "sdl".to_string(),
|
||||
description: None,
|
||||
args: Default::default(),
|
||||
ty: "String".to_string(),
|
||||
deprecation: None,
|
||||
cache_control: Default::default(),
|
||||
external: false,
|
||||
requires: None,
|
||||
provides: None,
|
||||
},
|
||||
);
|
||||
fields
|
||||
},
|
||||
cache_control: Default::default(),
|
||||
extends: false,
|
||||
keys: None,
|
||||
},
|
||||
);
|
||||
|
||||
self.create_entity_type();
|
||||
|
||||
let query_root = self.types.get_mut(&self.query_type).unwrap();
|
||||
if let MetaType::Object { fields, .. } = query_root {
|
||||
fields.insert(
|
||||
"_service".to_string(),
|
||||
MetaField {
|
||||
name: "_service".to_string(),
|
||||
description: None,
|
||||
args: Default::default(),
|
||||
ty: "_Service!".to_string(),
|
||||
deprecation: None,
|
||||
cache_control: Default::default(),
|
||||
external: false,
|
||||
requires: None,
|
||||
provides: None,
|
||||
},
|
||||
);
|
||||
|
||||
fields.insert(
|
||||
"_entities".to_string(),
|
||||
MetaField {
|
||||
name: "_entities".to_string(),
|
||||
description: None,
|
||||
args: {
|
||||
let mut args = IndexMap::new();
|
||||
args.insert(
|
||||
"representations",
|
||||
MetaInputValue {
|
||||
name: "representations",
|
||||
description: None,
|
||||
ty: "[_Any!]!".to_string(),
|
||||
default_value: None,
|
||||
validator: None,
|
||||
},
|
||||
);
|
||||
args
|
||||
},
|
||||
ty: "[_Entity]!".to_string(),
|
||||
deprecation: None,
|
||||
cache_control: Default::default(),
|
||||
external: false,
|
||||
requires: None,
|
||||
provides: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -197,10 +197,29 @@ where
|
|||
mutation: Mutation,
|
||||
subscription: Subscription,
|
||||
) -> SchemaBuilder<Query, Mutation, Subscription> {
|
||||
SchemaBuilder {
|
||||
validation_mode: ValidationMode::Strict,
|
||||
query: QueryRoot {
|
||||
inner: query,
|
||||
disable_introspection: false,
|
||||
},
|
||||
mutation,
|
||||
subscription,
|
||||
registry: Self::create_registry(),
|
||||
data: Default::default(),
|
||||
complexity: None,
|
||||
depth: None,
|
||||
extensions: Default::default(),
|
||||
enable_federation: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn create_registry() -> Registry {
|
||||
let mut registry = Registry {
|
||||
types: Default::default(),
|
||||
directives: Default::default(),
|
||||
implements: Default::default(),
|
||||
is_empty_query: Query::is_empty(),
|
||||
query_type: Query::type_name().to_string(),
|
||||
mutation_type: if Mutation::is_empty() {
|
||||
None
|
||||
|
@ -278,21 +297,7 @@ where
|
|||
Subscription::create_type_info(&mut registry);
|
||||
}
|
||||
|
||||
SchemaBuilder {
|
||||
validation_mode: ValidationMode::Strict,
|
||||
query: QueryRoot {
|
||||
inner: query,
|
||||
disable_introspection: false,
|
||||
},
|
||||
mutation,
|
||||
subscription,
|
||||
registry,
|
||||
data: Default::default(),
|
||||
complexity: None,
|
||||
depth: None,
|
||||
extensions: Default::default(),
|
||||
enable_federation: false,
|
||||
}
|
||||
registry
|
||||
}
|
||||
|
||||
/// Create a schema
|
||||
|
@ -304,6 +309,11 @@ where
|
|||
Self::build(query, mutation, subscription).finish()
|
||||
}
|
||||
|
||||
/// Returns SDL(Schema Definition Language) of this schema.
|
||||
pub fn sdl() -> String {
|
||||
Self::create_registry().export_sdl(false)
|
||||
}
|
||||
|
||||
// TODO: Remove the allow
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn prepare_request(
|
||||
|
|
|
@ -130,7 +130,7 @@ impl<T: ObjectType + Send + Sync> ObjectType for QueryRoot<T> {
|
|||
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
|
||||
return OutputValueType::resolve(
|
||||
&Service {
|
||||
sdl: Some(ctx.schema_env.registry.create_federation_sdl()),
|
||||
sdl: Some(ctx.schema_env.registry.export_sdl(true)),
|
||||
},
|
||||
&ctx_obj,
|
||||
ctx.item,
|
||||
|
|
Loading…
Reference in New Issue
Block a user