Add Schema::sdl method. #191

This commit is contained in:
Sunli 2020-09-23 08:04:00 +08:00
parent 6947d95030
commit 312b8f13d8
5 changed files with 315 additions and 267 deletions

204
src/registry/export_sdl.rs Normal file
View 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)
}
}

View File

@ -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)
}
}

View File

@ -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,
},
);
}
}
}

View File

@ -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(

View File

@ -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,