From 338960f85219945fe5393de431a8f73a54eefe32 Mon Sep 17 00:00:00 2001 From: Christian Legnitto Date: Wed, 6 Feb 2019 12:58:50 -0800 Subject: [PATCH 1/7] Add support for GraphQL Schema Language --- README.md | 4 +- juniper/CHANGELOG.md | 26 +- juniper/Cargo.toml | 4 + juniper/src/lib.rs | 9 +- juniper/src/schema/meta.rs | 32 +++ juniper/src/schema/mod.rs | 1 + juniper/src/schema/model.rs | 116 +++++++- .../src/schema/translate/graphql_parser.rs | 268 ++++++++++++++++++ juniper/src/schema/translate/mod.rs | 9 + 9 files changed, 458 insertions(+), 11 deletions(-) create mode 100644 juniper/src/schema/translate/graphql_parser.rs create mode 100644 juniper/src/schema/translate/mod.rs diff --git a/README.md b/README.md index 3444d2991..b06b6cd41 100644 --- a/README.md +++ b/README.md @@ -46,8 +46,7 @@ see the [hyper][hyper_examples], [rocket][rocket_examples], [iron][iron_examples Juniper supports the full GraphQL query language according to the [specification][graphql_spec], including interfaces, unions, schema -introspection, and validations. -It does not, however, support the schema language. +introspection, and validations. It can also output the schema in the [GraphQL Schema Language][schema_language]. As an exception to other GraphQL libraries for other languages, Juniper builds non-null types by default. A field of type `Vec` will be converted into @@ -86,6 +85,7 @@ Juniper has not reached 1.0 yet, thus some API instability should be expected. [playground]: https://github.com/prisma/graphql-playground [iron]: http://ironframework.io [graphql_spec]: http://facebook.github.io/graphql +[schema_language]: https://graphql.org/learn/schema/#type-language [test_schema_rs]: https://github.com/graphql-rust/juniper/blob/master/juniper/src/tests/schema.rs [tokio]: https://github.com/tokio-rs/tokio [hyper_examples]: https://github.com/graphql-rust/juniper/tree/master/juniper_hyper/examples diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index 93356570d..26de674c6 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -2,13 +2,31 @@ - The minimum required Rust version is now `1.30.0`. - The `ScalarValue` custom derive has been renamed to `GraphQLScalarValue`. -- Fix introspection query validity - The DirectiveLocation::InlineFragment had an invalid literal value, +- Fix introspection query validity. + `DirectiveLocation::InlineFragment` had an invalid literal value, which broke third party tools like apollo cli. - Added GraphQL Playground integration - The DirectiveLocation::InlineFragment had an invalid literal value, - which broke third party tools like apollo cli. - The return type of `value::object::Object::iter/iter_mut` has changed to `impl Iter` [#312](https://github.com/graphql-rust/juniper/pull/312) +- Added the `schema-language` feature (on by default). This feature enables converting + the schema to a `String` in the + [GraphQL Schema Language](https://graphql.org/learn/schema/#type-language) + format (usually written to a file called `schema.graphql`). + + Example: + + ```rust + use juniper::{RootNode, EmptyMutation}; + + #[derive(GraphQLObject)] + struct Query{ + foo: bool + }; + let s = RootNode::new(Query, EmptyMutation::<()>::new()); + println!("{}, s.as_schema_language()); + ``` + + Note: The `schema-language` feature brings in more dependencies. + If you don't use the schema language you may want to turn the feature off. # [0.11.1] 2018-12-19 diff --git a/juniper/Cargo.toml b/juniper/Cargo.toml index fec422031..f9d68f5cb 100644 --- a/juniper/Cargo.toml +++ b/juniper/Cargo.toml @@ -24,10 +24,13 @@ path = "benches/bench.rs" [features] nightly = [] expose-test-schema = [] +schema-language = ["graphql-parser-integration"] +graphql-parser-integration = ["graphql-parser"] default = [ "chrono", "url", "uuid", + "schema-language", ] [dependencies] @@ -42,6 +45,7 @@ chrono = { version = "0.4.0", optional = true } serde_json = { version="1.0.2", optional = true } url = { version = "1.5.1", optional = true } uuid = { version = "0.7", optional = true } +graphql-parser = {version = "0.2.2", optional = true } [dev-dependencies] bencher = "0.1.2" diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index 6d37632d0..c819316b2 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -103,13 +103,16 @@ extern crate fnv; extern crate indexmap; -#[cfg(any(test, feature = "chrono"))] +#[cfg(feature = "graphql-parser-integration")] +extern crate graphql_parser; + +#[cfg(feature = "chrono")] extern crate chrono; -#[cfg(any(test, feature = "url"))] +#[cfg(feature = "url")] extern crate url; -#[cfg(any(test, feature = "uuid"))] +#[cfg(feature = "uuid")] extern crate uuid; // Depend on juniper_codegen and re-export everything in it. diff --git a/juniper/src/schema/meta.rs b/juniper/src/schema/meta.rs index a294e996d..4dba3d3c0 100644 --- a/juniper/src/schema/meta.rs +++ b/juniper/src/schema/meta.rs @@ -165,6 +165,14 @@ pub struct Field<'a, S> { pub deprecation_status: DeprecationStatus, } +impl<'a, S> Field<'a, S> { + /// Returns true if the type is built-in to GraphQL. + pub fn is_builtin(&self) -> bool { + // "used exclusively by GraphQL’s introspection system" + self.name.starts_with("__") + } +} + /// Metadata for an argument to a field #[derive(Debug, Clone)] pub struct Argument<'a, S> { @@ -178,6 +186,14 @@ pub struct Argument<'a, S> { pub default_value: Option>, } +impl<'a, S> Argument<'a, S> { + /// Returns true if the type is built-in to GraphQL. + pub fn is_builtin(&self) -> bool { + // "used exclusively by GraphQL’s introspection system" + self.name.starts_with("__") + } +} + /// Metadata for a single value in an enum #[derive(Debug, Clone)] pub struct EnumValue { @@ -364,6 +380,22 @@ impl<'a, S> MetaType<'a, S> { } } + /// Returns true if the type is built-in to GraphQL. + pub fn is_builtin(&self) -> bool { + if let Some(name) = self.name() { + // "used exclusively by GraphQL’s introspection system" + { + name.starts_with("__") || + // + name == "Boolean" || name == "String" || name == "Int" || name == "Float" || name == "ID" || + // Our custom empty mutation marker + name == "_EmptyMutation" + } + } else { + false + } + } + pub(crate) fn fields<'b>(&self, schema: &'b SchemaType) -> Option>> { schema .lookup_type(&self.as_type()) diff --git a/juniper/src/schema/mod.rs b/juniper/src/schema/mod.rs index ae361c990..d612f5cf3 100644 --- a/juniper/src/schema/mod.rs +++ b/juniper/src/schema/mod.rs @@ -1,3 +1,4 @@ pub mod meta; pub mod model; pub mod schema; +pub mod translate; diff --git a/juniper/src/schema/model.rs b/juniper/src/schema/model.rs index acd47cffc..b9134c8eb 100644 --- a/juniper/src/schema/model.rs +++ b/juniper/src/schema/model.rs @@ -4,7 +4,14 @@ use fnv::FnvHashMap; use ast::Type; use executor::{Context, Registry}; +#[cfg(feature = "graphql-parser-integration")] +use graphql_parser::schema::Document; use schema::meta::{Argument, InterfaceMeta, MetaType, ObjectMeta, PlaceholderMeta, UnionMeta}; +#[cfg(feature = "graphql-parser-integration")] +use schema::translate::graphql_parser::GraphQLParserTranslator; +#[cfg(feature = "graphql-parser-integration")] +use schema::translate::SchemaTranslator; + use types::base::GraphQLType; use types::name::Name; use value::{DefaultScalarValue, ScalarRefValue, ScalarValue}; @@ -35,8 +42,8 @@ where #[derive(Debug)] pub struct SchemaType<'a, S> { pub(crate) types: FnvHashMap>, - query_type_name: String, - mutation_type_name: Option, + pub(crate) query_type_name: String, + pub(crate) mutation_type_name: Option, directives: FnvHashMap>, } @@ -88,6 +95,22 @@ where { RootNode::new_with_info(query_obj, mutation_obj, (), ()) } + + #[cfg(feature = "schema-language")] + /// The schema definition as a `String` in the + /// [GraphQL Schema Language](https://graphql.org/learn/schema/#type-language) + /// format. + pub fn as_schema_language(&self) -> String { + let doc = self.as_parser_document(); + format!("{}", doc) + } + + #[cfg(feature = "graphql-parser-integration")] + /// The schema definition as a [`graphql_parser`](https://crates.io/crates/graphql-parser) + /// [`Document`](https://docs.rs/graphql-parser/latest/graphql_parser/schema/struct.Document.html). + pub fn as_parser_document(&self) -> Document { + GraphQLParserTranslator::translate_schema(&self.schema) + } } impl<'a, S, QueryT, MutationT> RootNode<'a, QueryT, MutationT, S> @@ -466,3 +489,92 @@ impl<'a, S> fmt::Display for TypeType<'a, S> { } } } + +#[cfg(test)] +mod test { + + #[cfg(feature = "graphql-parser-integration")] + mod graphql_parser_integration { + use EmptyMutation; + + #[test] + fn graphql_parser_doc() { + struct Query; + graphql_object!(Query: () |&self| { + field blah() -> bool { + true + } + }); + let schema = crate::RootNode::new(Query, EmptyMutation::<()>::new()); + let ast = graphql_parser::parse_schema( + r#" + type Query { + blah: Boolean! + } + + schema { + query: Query + } + "#, + ) + .unwrap(); + assert_eq!( + format!("{}", ast), + format!("{}", schema.as_parser_document()), + ); + } + } + + #[cfg(feature = "schema-language")] + mod schema_language { + use EmptyMutation; + + #[test] + fn schema_language() { + struct Query; + graphql_object!(Query: () |&self| { + field blah() -> bool { + true + } + /// This is whatever's description. + field whatever() -> String { + "foo".to_string() + } + field fizz(buzz: String) -> Option<&str> { + None + } + field arr(stuff: Vec) -> Option<&str> { + None + } + #[deprecated] + field old() -> i32 { + 42 + } + #[deprecated(note="This field is deprecated, use another.")] + field really_old() -> f64 { + 42.0 + } + }); + let schema = crate::RootNode::new(Query, EmptyMutation::<()>::new()); + let ast = graphql_parser::parse_schema( + r#" + type Query { + blah: Boolean! + "This is whatever's description." + whatever: String! + fizz(buzz: String!): String + arr(stuff: [String!]!): String + old: Int! @deprecated + reallyOld: Float! @deprecated(reason: "This field is deprecated, use another.") + } + + schema { + query: Query + } + "#, + ) + .unwrap(); + assert_eq!(format!("{}", ast), schema.as_schema_language()); + } + } +} diff --git a/juniper/src/schema/translate/graphql_parser.rs b/juniper/src/schema/translate/graphql_parser.rs new file mode 100644 index 000000000..7902716c1 --- /dev/null +++ b/juniper/src/schema/translate/graphql_parser.rs @@ -0,0 +1,268 @@ +use std::boxed::Box; +use std::collections::BTreeMap; + +use graphql_parser::query::{ + Directive as ExternalDirective, Number as ExternalNumber, Type as ExternalType, +}; +use graphql_parser::schema::{Definition, Document, SchemaDefinition}; +use graphql_parser::schema::{ + EnumType as ExternalEnum, EnumValue as ExternalEnumValue, Field as ExternalField, + InputObjectType as ExternalInputObjectType, InputValue as ExternalInputValue, + InterfaceType as ExternalInterfaceType, ObjectType as ExternalObjectType, + ScalarType as ExternalScalarType, TypeDefinition as ExternalTypeDefinition, + UnionType as ExternalUnionType, Value as ExternalValue, +}; +use graphql_parser::Pos; + +use ast::{InputValue, Type}; +use schema::meta::DeprecationStatus; +use schema::meta::{Argument, EnumValue, Field, MetaType}; +use schema::model::SchemaType; +use schema::translate::SchemaTranslator; +use value::ScalarValue; + +pub struct GraphQLParserTranslator; + +impl<'a, S> From> for Document +where + S: ScalarValue, +{ + fn from(input: SchemaType<'a, S>) -> Document { + GraphQLParserTranslator::translate_schema(&input) + } +} + +impl SchemaTranslator for GraphQLParserTranslator { + fn translate_schema<'a, S>(input: &SchemaType<'a, S>) -> graphql_parser::schema::Document + where + S: ScalarValue, + { + let mut doc = Document::default(); + + // Translate type defs. + let mut types = input + .types + .iter() + .filter(|(_, meta)| !meta.is_builtin()) + .map(|(_, meta)| GraphQLParserTranslator::translate_meta(meta)) + .map(|x| Definition::TypeDefinition(x)) + .collect::>(); + doc.definitions.append(&mut types); + + doc.definitions + .push(Definition::SchemaDefinition(SchemaDefinition { + position: Pos::default(), + directives: vec![], + query: Some(input.query_type_name.clone()), + mutation: input.mutation_type_name.clone(), + // TODO: implement once we support subscriptions. + subscription: None, + })); + + doc + } +} + +impl GraphQLParserTranslator { + fn translate_argument<'a, S>(input: &Argument<'a, S>) -> ExternalInputValue + where + S: ScalarValue, + { + ExternalInputValue { + position: Pos::default(), + description: input.description.clone(), + name: input.name.clone(), + value_type: GraphQLParserTranslator::translate_type(&input.arg_type), + default_value: match input.default_value { + None => None, + Some(ref v) => Some(GraphQLParserTranslator::translate_value(v)), + }, + directives: vec![], + } + } + + fn translate_value(input: &InputValue) -> ExternalValue + where + S: ScalarValue, + { + match input { + InputValue::Null => ExternalValue::Null, + InputValue::Scalar(x) => { + if let Some(v) = x.as_string() { + ExternalValue::String(v) + } else if let Some(v) = x.as_int() { + ExternalValue::Int(ExternalNumber::from(v)) + } else if let Some(v) = x.as_float() { + ExternalValue::Float(v) + } else if let Some(v) = x.as_boolean() { + ExternalValue::Boolean(v) + } else { + panic!("unknown argument type") + } + } + InputValue::Enum(x) => ExternalValue::Enum(x.clone()), + InputValue::Variable(x) => ExternalValue::Variable(x.clone()), + InputValue::List(x) => ExternalValue::List( + x.iter() + .map(|s| GraphQLParserTranslator::translate_value(&s.item)) + .collect(), + ), + InputValue::Object(x) => { + let mut fields = BTreeMap::new(); + x.iter().for_each(|(name_span, value_span)| { + fields.insert( + name_span.item.clone(), + GraphQLParserTranslator::translate_value(&value_span.item), + ); + }); + ExternalValue::Object(fields) + } + } + } + + fn translate_type(input: &Type) -> ExternalType { + match input { + Type::Named(x) => ExternalType::NamedType(x.as_ref().to_string()), + Type::List(x) => ExternalType::ListType(Box::new( + GraphQLParserTranslator::translate_type(x.as_ref()), + )), + Type::NonNullNamed(x) => { + ExternalType::NonNullType(Box::new(ExternalType::NamedType(x.as_ref().to_string()))) + } + Type::NonNullList(x) => ExternalType::NonNullType(Box::new(ExternalType::ListType( + Box::new(GraphQLParserTranslator::translate_type(x.as_ref())), + ))), + } + } + + fn translate_meta<'empty, S>(input: &MetaType<'empty, S>) -> ExternalTypeDefinition + where + S: ScalarValue, + { + match input { + MetaType::Scalar(x) => ExternalTypeDefinition::Scalar(ExternalScalarType { + position: Pos::default(), + description: x.description.clone(), + name: x.name.to_string(), + directives: vec![], + }), + MetaType::Enum(x) => ExternalTypeDefinition::Enum(ExternalEnum { + position: Pos::default(), + description: x.description.clone(), + name: x.name.to_string(), + directives: vec![], + values: x + .values + .iter() + .map(GraphQLParserTranslator::translate_enum_value) + .collect(), + }), + MetaType::Union(x) => ExternalTypeDefinition::Union(ExternalUnionType { + position: Pos::default(), + description: x.description.clone(), + name: x.name.to_string(), + directives: vec![], + types: x.of_type_names.clone(), + }), + MetaType::Interface(x) => ExternalTypeDefinition::Interface(ExternalInterfaceType { + position: Pos::default(), + description: x.description.clone(), + name: x.name.to_string(), + directives: vec![], + fields: x + .fields + .iter() + .filter(|x| !x.is_builtin()) + .map(GraphQLParserTranslator::translate_field) + .collect(), + }), + MetaType::InputObject(x) => { + ExternalTypeDefinition::InputObject(ExternalInputObjectType { + position: Pos::default(), + description: x.description.clone(), + name: x.name.to_string(), + directives: vec![], + fields: x + .input_fields + .iter() + .filter(|x| !x.is_builtin()) + .map(GraphQLParserTranslator::translate_argument) + .collect(), + }) + } + MetaType::Object(x) => ExternalTypeDefinition::Object(ExternalObjectType { + position: Pos::default(), + description: x.description.clone(), + name: x.name.to_string(), + directives: vec![], + fields: x + .fields + .iter() + .filter(|x| !x.is_builtin()) + .map(GraphQLParserTranslator::translate_field) + .collect(), + implements_interfaces: x.interface_names.clone(), + }), + _ => panic!("unknown meta type when translating"), + } + } + + fn translate_enum_value(input: &EnumValue) -> ExternalEnumValue { + ExternalEnumValue { + position: Pos::default(), + name: input.name.clone(), + description: input.description.clone(), + directives: generate_directives(&input.deprecation_status), + } + } + + fn translate_field(input: &Field) -> ExternalField + where + S: ScalarValue, + { + ExternalField { + position: Pos::default(), + name: input.name.clone(), + description: input.description.clone(), + directives: generate_directives(&input.deprecation_status), + field_type: GraphQLParserTranslator::translate_type(&input.field_type), + arguments: input + .clone() + .arguments + .unwrap_or(vec![]) + .iter() + .filter(|x| !x.is_builtin()) + .map(GraphQLParserTranslator::translate_argument) + .collect(), + } + } +} + +fn deprecation_to_directive(status: &DeprecationStatus) -> Option { + match status { + DeprecationStatus::Current => None, + DeprecationStatus::Deprecated(reason) => Some(ExternalDirective { + position: Pos::default(), + name: "deprecated".to_string(), + arguments: if let Some(reason) = reason { + vec![( + "reason".to_string(), + ExternalValue::String(reason.to_string()), + )] + } else { + vec![] + }, + }), + } +} + +// Right now the only directive supported is `@deprecated`. `@skip` and `@include` +// are dealt with elsewhere. +// +fn generate_directives(status: &DeprecationStatus) -> Vec { + if let Some(d) = deprecation_to_directive(&status) { + vec![d] + } else { + vec![] + } +} diff --git a/juniper/src/schema/translate/mod.rs b/juniper/src/schema/translate/mod.rs new file mode 100644 index 000000000..a582e394a --- /dev/null +++ b/juniper/src/schema/translate/mod.rs @@ -0,0 +1,9 @@ +use schema::model::SchemaType; +use value::ScalarValue; + +pub trait SchemaTranslator { + fn translate_schema<'a, S: ScalarValue>(s: &SchemaType<'a, S>) -> T; +} + +#[cfg(feature = "graphql-parser-integration")] +pub mod graphql_parser; From 22f58ace29a5d87d635593224c6025d7f33d4584 Mon Sep 17 00:00:00 2001 From: Christian Legnitto Date: Fri, 8 Mar 2019 14:53:29 -0800 Subject: [PATCH 2/7] Add an enum to schema language test --- juniper/src/schema/model.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/juniper/src/schema/model.rs b/juniper/src/schema/model.rs index b9134c8eb..0cfd09da0 100644 --- a/juniper/src/schema/model.rs +++ b/juniper/src/schema/model.rs @@ -527,10 +527,16 @@ mod test { #[cfg(feature = "schema-language")] mod schema_language { + use crate as juniper; use EmptyMutation; #[test] fn schema_language() { + #[derive(GraphQLEnum)] + enum Fruit { + Apple, + Orange, + } struct Query; graphql_object!(Query: () |&self| { field blah() -> bool { @@ -546,6 +552,9 @@ mod test { field arr(stuff: Vec) -> Option<&str> { None } + field fruit() -> Fruit { + Fruit::Apple + } #[deprecated] field old() -> i32 { 42 @@ -558,12 +567,18 @@ mod test { let schema = crate::RootNode::new(Query, EmptyMutation::<()>::new()); let ast = graphql_parser::parse_schema( r#" + enum Fruit { + APPLE + ORANGE + } + type Query { blah: Boolean! "This is whatever's description." whatever: String! fizz(buzz: String!): String arr(stuff: [String!]!): String + fruit: Fruit! old: Int! @deprecated reallyOld: Float! @deprecated(reason: "This field is deprecated, use another.") } From ee767cef7872cb6a393c1b1e770a518f7676c490 Mon Sep 17 00:00:00 2001 From: Christian Legnitto Date: Fri, 8 Mar 2019 16:35:25 -0800 Subject: [PATCH 3/7] Add an interface to schema language test --- juniper/src/schema/model.rs | 50 +++++++++++++++++++++++++++++++------ 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/juniper/src/schema/model.rs b/juniper/src/schema/model.rs index 0cfd09da0..38791a318 100644 --- a/juniper/src/schema/model.rs +++ b/juniper/src/schema/model.rs @@ -532,11 +532,34 @@ mod test { #[test] fn schema_language() { + #[derive(GraphQLObject, Default)] + struct Cake { + fresh: bool, + }; + #[derive(GraphQLObject, Default)] + struct IceCream{ + cold: bool, + }; + enum Sweet { + Cake(Cake), + IceCream(IceCream), + } + graphql_interface!(Sweet: () where Scalar = |&self| { + instance_resolvers: |_| { + &Cake => match *self { Sweet::Cake(ref x) => Some(x), _ => None }, + &IceCream => match *self { Sweet::IceCream(ref x) => Some(x), _ => None }, + } + }); #[derive(GraphQLEnum)] enum Fruit { Apple, Orange, } + #[derive(GraphQLInputObject)] + struct Coordinate { + latitude: f64, + longitude: f64 + } struct Query; graphql_object!(Query: () |&self| { field blah() -> bool { @@ -546,10 +569,14 @@ mod test { field whatever() -> String { "foo".to_string() } - field fizz(buzz: String) -> Option<&str> { - None + field fizz(buzz: String) -> Option { + if buzz == "whatever" { + Some(Sweet::Cake(Cake::default())) + } else { + Some(Sweet::IceCream(IceCream::default())) + } } - field arr(stuff: Vec) -> Option<&str> { + field arr(stuff: Vec) -> Option<&str> { None } field fruit() -> Fruit { @@ -571,18 +598,27 @@ mod test { APPLE ORANGE } - + interface Sweet + type Cake { + fresh: Boolean! + } + type IceCream { + cold: Boolean! + } type Query { blah: Boolean! "This is whatever's description." whatever: String! - fizz(buzz: String!): String - arr(stuff: [String!]!): String + fizz(buzz: String!): Sweet + arr(stuff: [Coordinate!]!): String fruit: Fruit! old: Int! @deprecated reallyOld: Float! @deprecated(reason: "This field is deprecated, use another.") } - + input Coordinate { + latitude: Float! + longitude: Float! + } schema { query: Query } From 65cb4cabfed79cb076b96d3e8eb63ad17d75510d Mon Sep 17 00:00:00 2001 From: Christian Legnitto Date: Fri, 8 Mar 2019 16:48:27 -0800 Subject: [PATCH 4/7] Fix formatting --- juniper/src/schema/model.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/juniper/src/schema/model.rs b/juniper/src/schema/model.rs index 38791a318..25824e22a 100644 --- a/juniper/src/schema/model.rs +++ b/juniper/src/schema/model.rs @@ -537,7 +537,7 @@ mod test { fresh: bool, }; #[derive(GraphQLObject, Default)] - struct IceCream{ + struct IceCream { cold: bool, }; enum Sweet { @@ -558,7 +558,7 @@ mod test { #[derive(GraphQLInputObject)] struct Coordinate { latitude: f64, - longitude: f64 + longitude: f64, } struct Query; graphql_object!(Query: () |&self| { From 85554fc09b1798c27a34846a8865b2e21336d14b Mon Sep 17 00:00:00 2001 From: Christian Legnitto Date: Fri, 8 Mar 2019 16:58:05 -0800 Subject: [PATCH 5/7] Add a field to example interface --- juniper/src/schema/model.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/juniper/src/schema/model.rs b/juniper/src/schema/model.rs index 25824e22a..bf6126219 100644 --- a/juniper/src/schema/model.rs +++ b/juniper/src/schema/model.rs @@ -545,6 +545,7 @@ mod test { IceCream(IceCream), } graphql_interface!(Sweet: () where Scalar = |&self| { + field is_brownie() -> bool { false } instance_resolvers: |_| { &Cake => match *self { Sweet::Cake(ref x) => Some(x), _ => None }, &IceCream => match *self { Sweet::IceCream(ref x) => Some(x), _ => None }, @@ -598,7 +599,9 @@ mod test { APPLE ORANGE } - interface Sweet + interface Sweet { + isBrownie: Boolean! + } type Cake { fresh: Boolean! } From 15239148b7e38392140c29e36d8feefba65ef742 Mon Sep 17 00:00:00 2001 From: Christian Legnitto Date: Fri, 8 Mar 2019 17:03:53 -0800 Subject: [PATCH 6/7] Add a union to schema language test --- juniper/src/schema/model.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/juniper/src/schema/model.rs b/juniper/src/schema/model.rs index bf6126219..b73941417 100644 --- a/juniper/src/schema/model.rs +++ b/juniper/src/schema/model.rs @@ -544,6 +544,10 @@ mod test { Cake(Cake), IceCream(IceCream), } + enum GlutenFree { + Cake(Cake), + IceCream(IceCream), + } graphql_interface!(Sweet: () where Scalar = |&self| { field is_brownie() -> bool { false } instance_resolvers: |_| { @@ -551,6 +555,12 @@ mod test { &IceCream => match *self { Sweet::IceCream(ref x) => Some(x), _ => None }, } }); + graphql_union!(GlutenFree: () where Scalar = |&self| { + instance_resolvers: |_| { + &Cake => match *self { GlutenFree::Cake(ref x) => Some(x), _ => None }, + &IceCream => match *self { GlutenFree::IceCream(ref x) => Some(x), _ => None }, + } + }); #[derive(GraphQLEnum)] enum Fruit { Apple, @@ -583,6 +593,12 @@ mod test { field fruit() -> Fruit { Fruit::Apple } + field gluten_free(flavor: String) -> GlutenFree { + if flavor == "savory" { + GlutenFree::Cake(Cake::default()) + } else { + GlutenFree::IceCream(IceCream::default()) + } } #[deprecated] field old() -> i32 { 42 @@ -595,6 +611,7 @@ mod test { let schema = crate::RootNode::new(Query, EmptyMutation::<()>::new()); let ast = graphql_parser::parse_schema( r#" + union GlutenFree = Cake | IceCream enum Fruit { APPLE ORANGE @@ -615,6 +632,7 @@ mod test { fizz(buzz: String!): Sweet arr(stuff: [Coordinate!]!): String fruit: Fruit! + glutenFree(flavor: String!): GlutenFree! old: Int! @deprecated reallyOld: Float! @deprecated(reason: "This field is deprecated, use another.") } From caa700867dd75c2d74a4b6928fd123dfe605bab2 Mon Sep 17 00:00:00 2001 From: Christian Legnitto Date: Fri, 8 Mar 2019 17:11:10 -0800 Subject: [PATCH 7/7] Add a default argument and description to schema language test --- juniper/src/schema/model.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/juniper/src/schema/model.rs b/juniper/src/schema/model.rs index b73941417..4030d9bad 100644 --- a/juniper/src/schema/model.rs +++ b/juniper/src/schema/model.rs @@ -580,7 +580,7 @@ mod test { field whatever() -> String { "foo".to_string() } - field fizz(buzz: String) -> Option { + field fizz(buzz: String as "Buzz description") -> Option { if buzz == "whatever" { Some(Sweet::Cake(Cake::default())) } else { @@ -593,8 +593,8 @@ mod test { field fruit() -> Fruit { Fruit::Apple } - field gluten_free(flavor: String) -> GlutenFree { - if flavor == "savory" { + field gluten_free(people = 1: i32 ) -> GlutenFree { + if people > 1 { GlutenFree::Cake(Cake::default()) } else { GlutenFree::IceCream(IceCream::default()) @@ -629,10 +629,10 @@ mod test { blah: Boolean! "This is whatever's description." whatever: String! - fizz(buzz: String!): Sweet + fizz("Buzz description" buzz: String!): Sweet arr(stuff: [Coordinate!]!): String fruit: Fruit! - glutenFree(flavor: String!): GlutenFree! + glutenFree(people: Int = 1): GlutenFree! old: Int! @deprecated reallyOld: Float! @deprecated(reason: "This field is deprecated, use another.") }