mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-15 01:12:56 +03:00
SDL schema generation improvements (#642)
Add the ability to render field input-arguments and default values in sdl. Also add a small CLI tool to help inspect the GraphQL schemas that result from a piece of engine metadata. V3_GIT_ORIGIN_REV_ID: ddb7315f12d24eafbf57782f37f840ca44d94789
This commit is contained in:
parent
6964b7cf3d
commit
812f44f3c1
@ -3,7 +3,7 @@
|
||||
"__typename": "Query",
|
||||
"_service": {
|
||||
"__typename": "_Service",
|
||||
"sdl": "extend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.0\", import: [\"@key\", \"@extends\", \"@external\", \"@shareable\"])\n\nschema {\n query: Query \n}\n\ntype Author@key(fields: \"author_id\") {\n author_id: Int! \n first_name: String! \n last_name: String! \n}\n\nscalar Boolean\n\nscalar Float\n\nscalar ID\n\nscalar Int\n\ntype Query {\n AuthorByID: Author \n _entities: _Entity! \n _service: _Service! \n}\n\nscalar String\n\nscalar _Any\n\nunion _Entity = Author\n\ntype _Service {\n sdl: String! \n}"
|
||||
"sdl": "extend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.0\", import: [\"@key\", \"@extends\", \"@external\", \"@shareable\"])\n\nschema {\n query: Query \n}\n\ntype Author@key(fields: \"author_id\") {\n author_id: Int!\n first_name: String!\n last_name: String!\n}\n\nscalar Boolean\n\nscalar Float\n\nscalar ID\n\nscalar Int\n\ntype Query {\n AuthorByID(author_id: Int!): Author\n _entities(representations: [_Any!]!): _Entity!\n _service: _Service!\n}\n\nscalar String\n\nscalar _Any\n\nunion _Entity = Author\n\ntype _Service {\n sdl: String!\n}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,10 +40,10 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::{
|
||||
ast::common as ast,
|
||||
ast::{common as ast, value::ConstValue},
|
||||
schema::{
|
||||
DeprecationStatus, Directive, Enum, Field, InputObject, Interface, Namespaced, Object,
|
||||
Scalar, Schema, SchemaContext, TypeInfo, Union,
|
||||
DeprecationStatus, Directive, Enum, Field, InputField, InputObject, Interface, Namespaced,
|
||||
Object, Scalar, Schema, SchemaContext, TypeInfo, Union,
|
||||
},
|
||||
};
|
||||
|
||||
@ -95,10 +95,12 @@ impl<S: SchemaContext> InputObject<S> {
|
||||
field.get(namespace).map(|(data, _)| {
|
||||
with_description(
|
||||
&data.description,
|
||||
format_field_with_type(
|
||||
format_field_with_type::<S>(
|
||||
field_name,
|
||||
&BTreeMap::new(),
|
||||
&data.field_type,
|
||||
Some(&data.deprecation_status),
|
||||
&data.deprecation_status,
|
||||
namespace,
|
||||
),
|
||||
)
|
||||
})
|
||||
@ -388,8 +390,10 @@ fn generate_fields_sdl<S: SchemaContext>(
|
||||
&data.description,
|
||||
format_field_with_type(
|
||||
field_name,
|
||||
&data.arguments,
|
||||
&data.field_type,
|
||||
Some(&data.deprecation_status),
|
||||
&data.deprecation_status,
|
||||
namespace,
|
||||
),
|
||||
));
|
||||
}
|
||||
@ -398,22 +402,115 @@ fn generate_fields_sdl<S: SchemaContext>(
|
||||
fields_sdl
|
||||
}
|
||||
|
||||
fn format_field_with_type(
|
||||
/// Format an input field as SDL, including default values if present.
|
||||
///
|
||||
/// Syntax:
|
||||
///
|
||||
/// <field_name>: <field_type> = <default_value>
|
||||
///
|
||||
fn format_input_field_with_type(
|
||||
field_name: &ast::Name,
|
||||
field_type: &ast::TypeContainer<ast::TypeName>,
|
||||
deprecation_status: Option<&DeprecationStatus>,
|
||||
deprecation_status: &DeprecationStatus,
|
||||
default_value: Option<&ConstValue>,
|
||||
) -> String {
|
||||
let field_sdl = format!("{}: {}", field_name, field_type);
|
||||
match deprecation_status {
|
||||
Some(deprecation_status) => format!(
|
||||
let field_sdl = match default_value {
|
||||
None => format!("{}: {}", field_name, field_type),
|
||||
Some(default_value) => {
|
||||
let default_value_sdl = default_value.to_json().to_string();
|
||||
let mut default_value_lines = default_value_sdl.lines();
|
||||
let multiple_lines = {
|
||||
default_value_lines.next();
|
||||
default_value_lines.next().is_some()
|
||||
};
|
||||
if multiple_lines {
|
||||
format!(
|
||||
"{}: {}\n = {}",
|
||||
field_name,
|
||||
field_type,
|
||||
with_indent(default_value_sdl.as_str())
|
||||
)
|
||||
} else {
|
||||
format!("{}: {} = {}", field_name, field_type, default_value_sdl)
|
||||
}
|
||||
}
|
||||
};
|
||||
if deprecation_status == &DeprecationStatus::NotDeprecated {
|
||||
field_sdl
|
||||
} else {
|
||||
format!(
|
||||
"{} {}",
|
||||
field_sdl,
|
||||
generate_directives_sdl(&[], Some(deprecation_status))
|
||||
),
|
||||
_ => field_sdl,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Format an (output) field as SDL, including field arguments if present.
|
||||
///
|
||||
/// Syntax:
|
||||
///
|
||||
/// <field_name>(<generate_arguments_sdl(field_arguments)>): <field_type>
|
||||
///
|
||||
fn format_field_with_type<S: SchemaContext>(
|
||||
field_name: &ast::Name,
|
||||
field_arguments: &BTreeMap<ast::Name, Namespaced<S, InputField<S>>>,
|
||||
field_type: &ast::TypeContainer<ast::TypeName>,
|
||||
deprecation_status: &DeprecationStatus,
|
||||
namespace: &S::Namespace,
|
||||
) -> String {
|
||||
let field_sdl = if field_arguments.is_empty() {
|
||||
format!("{}: {}", field_name, field_type)
|
||||
} else {
|
||||
let arguments_sdl = generate_arguments_sdl(field_arguments, namespace);
|
||||
let mut arguments_lines = arguments_sdl.lines();
|
||||
let multiple_lines = {
|
||||
arguments_lines.next();
|
||||
arguments_lines.next().is_some()
|
||||
};
|
||||
|
||||
if multiple_lines {
|
||||
format!(
|
||||
"{}(\n{}\n ): {}",
|
||||
field_name,
|
||||
with_indent(arguments_sdl.as_str()),
|
||||
field_type
|
||||
)
|
||||
} else {
|
||||
format!("{}({}): {}", field_name, arguments_sdl, field_type)
|
||||
}
|
||||
};
|
||||
if deprecation_status == &DeprecationStatus::NotDeprecated {
|
||||
field_sdl
|
||||
} else {
|
||||
format!(
|
||||
"{} {}",
|
||||
field_sdl,
|
||||
generate_directives_sdl(&[], Some(deprecation_status))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_arguments_sdl<S: SchemaContext>(
|
||||
field_arguments: &BTreeMap<ast::Name, Namespaced<S, InputField<S>>>,
|
||||
namespace: &S::Namespace,
|
||||
) -> String {
|
||||
field_arguments
|
||||
.iter()
|
||||
.filter_map(|(field_name, namespaced)| {
|
||||
namespaced.get(namespace).map(|(input_field, _)| {
|
||||
format_input_field_with_type(
|
||||
field_name,
|
||||
&input_field.field_type,
|
||||
&input_field.deprecation_status,
|
||||
input_field.default_value.as_ref(),
|
||||
)
|
||||
})
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join(",\n")
|
||||
}
|
||||
|
||||
fn with_description(description: &Option<String>, sdl: String) -> String {
|
||||
if description.is_some() {
|
||||
format!("{}\n{}", generate_description_sdl(description), sdl)
|
||||
|
@ -7,6 +7,11 @@ license.workspace = true
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[[bin]]
|
||||
name = "build-schema-from-metadata"
|
||||
path = "bin/build-schema-from-metadata/main.rs"
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
hasura-authn-core = { path = "../auth/hasura-authn-core" }
|
||||
json-ext = { path = "../utils/json-ext" }
|
||||
|
65
v3/crates/schema/bin/build-schema-from-metadata/main.rs
Normal file
65
v3/crates/schema/bin/build-schema-from-metadata/main.rs
Normal file
@ -0,0 +1,65 @@
|
||||
/// This is a small command-line utility that prints out the GraphQL schemas that result from an
|
||||
/// engine-metadata value.
|
||||
///
|
||||
use std::{
|
||||
collections::BTreeSet,
|
||||
io::{stdin, Read},
|
||||
process::exit,
|
||||
};
|
||||
|
||||
use hasura_authn_core::Role;
|
||||
use lang_graphql::generate_graphql_schema::build_namespace_schemas;
|
||||
|
||||
pub fn main() {
|
||||
let mut metadata_string = String::new();
|
||||
let mut h = stdin();
|
||||
|
||||
h.read_to_string(&mut metadata_string).unwrap();
|
||||
|
||||
if metadata_string.is_empty() {
|
||||
println!("Usage: Provide a metadata json file via stdin and get the corresponding sdl schema and introspection query results on stdout.");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
let metadata =
|
||||
open_dds::traits::OpenDd::deserialize(serde_json::from_str(&metadata_string).unwrap())
|
||||
.unwrap();
|
||||
let gds = schema::GDS::new(metadata).unwrap();
|
||||
let sch = gds.build_schema().unwrap();
|
||||
|
||||
let namespace_schemas = build_namespace_schemas(&sch).unwrap();
|
||||
|
||||
let dedup_roles: BTreeSet<Role> = gds.metadata.roles.into_iter().collect();
|
||||
|
||||
for role in dedup_roles {
|
||||
println!("-------------------------------:");
|
||||
println!("Now comes the SDL schema for {role}:");
|
||||
println!("-------------------------------:");
|
||||
println!();
|
||||
println!("{}", sch.generate_sdl(&role));
|
||||
println!();
|
||||
println!();
|
||||
println!();
|
||||
|
||||
print!("-------------------------------:");
|
||||
print!("Now comes the query-based? schema for {role}:");
|
||||
print!("-------------------------------:");
|
||||
println!();
|
||||
print!(
|
||||
"{}",
|
||||
serde_json::ser::to_string_pretty(namespace_schemas.get(&role).unwrap()).unwrap()
|
||||
);
|
||||
println!();
|
||||
println!();
|
||||
println!();
|
||||
}
|
||||
|
||||
println!("-------------------------------:");
|
||||
println!(" Debug print of schema :");
|
||||
println!("-------------------------------:");
|
||||
println!();
|
||||
println!("{:#?}", sch);
|
||||
println!();
|
||||
println!();
|
||||
println!();
|
||||
}
|
Loading…
Reference in New Issue
Block a user