Add some options for exporting SDL. #877

This commit is contained in:
Sunli 2022-05-05 20:09:35 +08:00
parent 7a365d5659
commit de4f908812
5 changed files with 133 additions and 31 deletions

View File

@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added impl of `CursorType` for floats [#897](https://github.com/async-graphql/async-graphql/pull/897)
- Implement `OutputType` for `tokio::sync::RwLock` and `tokio::sync::Mutex`. [#896](https://github.com/async-graphql/async-graphql/pull/896)
- Bump [`uuid`](https://crates.io/crates/uuid) to `1.0.0`. [#907](https://github.com/async-graphql/async-graphql/pull/907/files)
- Add some options for exporting SDL. [#877](https://github.com/async-graphql/async-graphql/issues/877)
# [3.0.38] 2022-4-8

View File

@ -2,8 +2,68 @@ use std::fmt::Write;
use crate::registry::{Deprecation, MetaField, MetaInputValue, MetaType, Registry};
const SYSTEM_SCALARS: &[&str] = &["Int", "Float", "String", "Boolean", "ID"];
const FEDERATION_SCALARS: &[&str] = &["Any"];
/// Options for SDL export
#[derive(Debug, Copy, Clone, Default)]
pub struct SDLExportOptions {
sorted_fields: bool,
sorted_arguments: bool,
sorted_enum_values: bool,
federation: bool,
}
impl SDLExportOptions {
/// Create a `SDLExportOptions`
#[inline]
pub fn new() -> Self {
Default::default()
}
/// Export sorted fields
#[inline]
#[must_use]
pub fn sorted_fields(self) -> Self {
Self {
sorted_fields: true,
..self
}
}
/// Export sorted field arguments
#[inline]
#[must_use]
pub fn sorted_arguments(self) -> Self {
Self {
sorted_arguments: true,
..self
}
}
/// Export sorted enum items
#[inline]
#[must_use]
pub fn sorted_enum_items(self) -> Self {
Self {
sorted_enum_values: true,
..self
}
}
/// Export as Federation SDL(Schema Definition Language)
#[inline]
#[must_use]
pub fn federation(self) -> Self {
Self {
federation: true,
..self
}
}
}
impl Registry {
pub fn export_sdl(&self, federation: bool) -> String {
pub(crate) fn export_sdl(&self, options: SDLExportOptions) -> String {
let mut sdl = String::new();
let has_oneof = self.types.values().any(|ty| match ty {
@ -22,17 +82,18 @@ impl Registry {
continue;
}
if federation {
if options.federation {
const FEDERATION_TYPES: &[&str] = &["_Any", "_Entity", "_Service"];
if FEDERATION_TYPES.contains(&ty.name()) {
continue;
}
}
self.export_type(ty, &mut sdl, federation);
self.export_type(ty, &mut sdl, &options);
writeln!(sdl).ok();
}
if !federation {
if !options.federation {
writeln!(sdl, "schema {{").ok();
writeln!(sdl, "\tquery: {}", self.query_type).ok();
if let Some(mutation_type) = self.mutation_type.as_deref() {
@ -50,11 +111,17 @@ impl Registry {
fn export_fields<'a, I: Iterator<Item = &'a MetaField>>(
sdl: &mut String,
it: I,
federation: bool,
options: &SDLExportOptions,
) {
for field in it {
let mut fields = it.collect::<Vec<_>>();
if options.sorted_fields {
fields.sort_by(|a, b| a.name.cmp(&b.name));
}
for field in fields {
if field.name.starts_with("__")
|| (federation && matches!(&*field.name, "_service" | "_entities"))
|| (options.federation && matches!(&*field.name, "_service" | "_entities"))
{
continue;
}
@ -67,9 +134,16 @@ impl Registry {
)
.ok();
}
if !field.args.is_empty() {
write!(sdl, "\t{}(", field.name).ok();
for (i, arg) in field.args.values().enumerate() {
let mut args = field.args.values().collect::<Vec<_>>();
if options.sorted_arguments {
args.sort_by(|a, b| a.name.cmp(b.name));
}
for (i, arg) in args.into_iter().enumerate() {
if i != 0 {
sdl.push_str(", ");
}
@ -86,7 +160,7 @@ impl Registry {
write_deprecated(sdl, &field.deprecation);
if federation {
if options.federation {
if field.external {
write!(sdl, " @external").ok();
}
@ -102,15 +176,13 @@ impl Registry {
}
}
fn export_type(&self, ty: &MetaType, sdl: &mut String, federation: bool) {
fn export_type(&self, ty: &MetaType, sdl: &mut String, options: &SDLExportOptions) {
match ty {
MetaType::Scalar {
name, description, ..
} => {
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()) {
if options.federation && FEDERATION_SCALARS.contains(&name.as_str()) {
export_scalar = false;
}
if export_scalar {
@ -129,23 +201,25 @@ impl Registry {
..
} => {
if Some(name.as_str()) == self.subscription_type.as_deref()
&& federation
&& options.federation
&& !self.federation_subscription
{
return;
}
if name.as_str() == self.query_type && federation {
if name.as_str() == self.query_type && options.federation {
let mut field_count = 0;
for field in fields.values() {
if field.name.starts_with("__")
|| (federation && matches!(&*field.name, "_service" | "_entities"))
|| (options.federation
&& matches!(&*field.name, "_service" | "_entities"))
{
continue;
}
field_count += 1;
}
if field_count == 0 {
// is empty query root type
return;
}
}
@ -153,13 +227,15 @@ impl Registry {
if description.is_some() {
writeln!(sdl, "\"\"\"\n{}\n\"\"\"", description.unwrap()).ok();
}
if federation && *extends {
if options.federation && *extends {
write!(sdl, "extend ").ok();
}
write!(sdl, "type {} ", name).ok();
self.write_implements(sdl, name);
if federation {
if options.federation {
if let Some(keys) = keys {
for key in keys {
write!(sdl, "@key(fields: \"{}\") ", key).ok();
@ -168,7 +244,7 @@ impl Registry {
}
writeln!(sdl, "{{").ok();
Self::export_fields(sdl, fields.values(), federation);
Self::export_fields(sdl, fields.values(), options);
writeln!(sdl, "}}").ok();
}
MetaType::Interface {
@ -182,11 +258,13 @@ impl Registry {
if description.is_some() {
writeln!(sdl, "\"\"\"\n{}\n\"\"\"", description.unwrap()).ok();
}
if federation && *extends {
if options.federation && *extends {
write!(sdl, "extend ").ok();
}
write!(sdl, "interface {} ", name).ok();
if federation {
if options.federation {
if let Some(keys) = keys {
for key in keys {
write!(sdl, "@key(fields: \"{}\") ", key).ok();
@ -196,7 +274,7 @@ impl Registry {
self.write_implements(sdl, name);
writeln!(sdl, "{{").ok();
Self::export_fields(sdl, fields.values(), federation);
Self::export_fields(sdl, fields.values(), options);
writeln!(sdl, "}}").ok();
}
MetaType::Enum {
@ -208,13 +286,21 @@ impl Registry {
if description.is_some() {
writeln!(sdl, "\"\"\"\n{}\n\"\"\"", description.unwrap()).ok();
}
write!(sdl, "enum {} ", name).ok();
writeln!(sdl, "{{").ok();
for value in enum_values.values() {
let mut values = enum_values.values().collect::<Vec<_>>();
if options.sorted_enum_values {
values.sort_by(|a, b| a.name.cmp(&b.name));
}
for value in values {
write!(sdl, "\t{}", value.name).ok();
write_deprecated(sdl, &value.deprecation);
writeln!(sdl).ok();
}
writeln!(sdl, "}}").ok();
}
MetaType::InputObject {
@ -227,17 +313,26 @@ impl Registry {
if description.is_some() {
writeln!(sdl, "\"\"\"\n{}\n\"\"\"", description.unwrap()).ok();
}
write!(sdl, "input {} ", name).ok();
if *oneof {
write!(sdl, "@oneof ").ok();
}
writeln!(sdl, "{{").ok();
for field in input_fields.values() {
let mut fields = input_fields.values().collect::<Vec<_>>();
if options.sorted_fields {
fields.sort_by(|a, b| a.name.cmp(b.name));
}
for field in fields {
if let Some(description) = field.description {
writeln!(sdl, "\t\"\"\"\n\t{}\n\t\"\"\"", description).ok();
}
writeln!(sdl, "\t{}", export_input_value(&field)).ok();
}
writeln!(sdl, "}}").ok();
}
MetaType::Union {
@ -249,6 +344,7 @@ impl Registry {
if description.is_some() {
writeln!(sdl, "\"\"\"\n{}\n\"\"\"", description.unwrap()).ok();
}
write!(sdl, "union {} =", name).ok();
for ty in possible_types {
write!(sdl, " | {}", ty).ok();

View File

@ -8,6 +8,7 @@ use std::{
};
pub use cache_control::CacheControl;
pub use export_sdl::SDLExportOptions;
use indexmap::{map::IndexMap, set::IndexSet};
pub use crate::model::__DirectiveLocation;

View File

@ -13,7 +13,7 @@ use crate::{
types::{Directive, DocumentOperations, OperationType, Selection, SelectionSet},
Positioned,
},
registry::{MetaDirective, MetaInputValue, Registry},
registry::{MetaDirective, MetaInputValue, Registry, SDLExportOptions},
resolver_utils::{resolve_container, resolve_container_serial},
subscription::collect_subscription_streams,
types::QueryRoot,
@ -421,12 +421,12 @@ where
/// Returns SDL(Schema Definition Language) of this schema.
pub fn sdl(&self) -> String {
self.0.env.registry.export_sdl(false)
self.0.env.registry.export_sdl(Default::default())
}
/// Returns Federation SDL(Schema Definition Language) of this schema.
pub fn federation_sdl(&self) -> String {
self.0.env.registry.export_sdl(true)
/// Returns SDL(Schema Definition Language) of this schema with options.
pub fn sdl_with_options(&self, options: SDLExportOptions) -> String {
self.0.env.registry.export_sdl(options)
}
/// Get all names in this schema

View File

@ -5,7 +5,7 @@ use indexmap::map::IndexMap;
use crate::{
model::{__Schema, __Type},
parser::types::Field,
registry,
registry::{self, SDLExportOptions},
resolver_utils::{resolve_container, ContainerType},
schema::IntrospectionMode,
Any, Context, ContextSelectionSet, ObjectType, OutputType, Positioned, ServerError,
@ -84,7 +84,11 @@ impl<T: ObjectType> ContainerType for QueryRoot<T> {
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
return OutputType::resolve(
&Service {
sdl: Some(ctx.schema_env.registry.export_sdl(true)),
sdl: Some(
ctx.schema_env
.registry
.export_sdl(SDLExportOptions::new().federation()),
),
},
&ctx_obj,
ctx.item,