From 3dfe79ef894275e7c552e2e8cdc92cdaef9ba7d2 Mon Sep 17 00:00:00 2001 From: Alexander Lyon Date: Fri, 17 Apr 2020 17:44:41 +0100 Subject: [PATCH 1/6] Add support for GraphQL Schema Language --- README.md | 4 +- juniper/Cargo.toml | 4 + juniper/src/schema/meta.rs | 32 ++ juniper/src/schema/mod.rs | 1 + juniper/src/schema/model.rs | 126 +++++++- .../src/schema/translate/graphql_parser.rs | 306 ++++++++++++++++++ juniper/src/schema/translate/mod.rs | 8 + 7 files changed, 476 insertions(+), 5 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 bbcef30ff..b2e126e1e 100644 --- a/README.md +++ b/README.md @@ -48,8 +48,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. Consider using [juniper-from-schema][] for generating code from a schema file. +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 @@ -89,6 +88,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/Cargo.toml b/juniper/Cargo.toml index faaf1b270..3f943acaa 100644 --- a/juniper/Cargo.toml +++ b/juniper/Cargo.toml @@ -25,11 +25,14 @@ path = "benches/bench.rs" [features] expose-test-schema = ["serde_json"] +schema-language = ["graphql-parser-integration"] +graphql-parser-integration = ["graphql-parser"] default = [ "bson", "chrono", "url", "uuid", + "schema-language", ] [dependencies] @@ -45,6 +48,7 @@ serde_derive = { version = "1.0.2" } serde_json = { version="1.0.2", optional = true } url = { version = "2", optional = true } uuid = { version = "0.8", optional = true } +graphql-parser = {version = "0.3.0", optional = true } [dev-dependencies] bencher = "0.1.2" diff --git a/juniper/src/schema/meta.rs b/juniper/src/schema/meta.rs index d86210356..bd5954d0d 100644 --- a/juniper/src/schema/meta.rs +++ b/juniper/src/schema/meta.rs @@ -169,6 +169,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> { @@ -182,6 +190,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 { @@ -368,6 +384,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" || name == "_EmptySubscription" + } + } 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 7f1658a93..d174e938b 100644 --- a/juniper/src/schema/mod.rs +++ b/juniper/src/schema/mod.rs @@ -3,3 +3,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 9eb747670..a72ce507f 100644 --- a/juniper/src/schema/model.rs +++ b/juniper/src/schema/model.rs @@ -2,12 +2,14 @@ use std::fmt; use fnv::FnvHashMap; +use graphql_parser::schema::Document; use juniper_codegen::GraphQLEnumInternal as GraphQLEnum; use crate::{ ast::Type, executor::{Context, Registry}, schema::meta::{Argument, InterfaceMeta, MetaType, ObjectMeta, PlaceholderMeta, UnionMeta}, + schema::translate::{graphql_parser::GraphQLParserTranslator, SchemaTranslator}, types::{base::GraphQLType, name::Name}, value::{DefaultScalarValue, ScalarValue}, }; @@ -46,9 +48,9 @@ pub struct RootNode< #[derive(Debug)] pub struct SchemaType<'a, S> { pub(crate) types: FnvHashMap>, - query_type_name: String, - mutation_type_name: Option, - subscription_type_name: Option, + pub(crate) query_type_name: String, + pub(crate) mutation_type_name: Option, + pub(crate) subscription_type_name: Option, directives: FnvHashMap>, } @@ -102,6 +104,22 @@ where ) -> Self { RootNode::new_with_info(query_obj, mutation_obj, subscription_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(&'a self) -> Document<'a, &'a str> { + GraphQLParserTranslator::translate_schema(&self.schema) + } } impl<'a, S, QueryT, MutationT, SubscriptionT> RootNode<'a, QueryT, MutationT, SubscriptionT, S> @@ -534,3 +552,105 @@ impl<'a, S> fmt::Display for TypeType<'a, S> { } } } + +#[cfg(test)] +mod test { + + #[cfg(feature = "graphql-parser-integration")] + mod graphql_parser_integration { + use crate as juniper; + use crate::{EmptyMutation, EmptySubscription}; + + #[test] + fn graphql_parser_doc() { + struct Query; + #[juniper::graphql_object] + impl Query { + fn blah() -> bool { + true + } + }; + let schema = crate::RootNode::new( + Query, + EmptyMutation::<()>::new(), + EmptySubscription::<()>::new(), + ); + let ast = graphql_parser::parse_schema::<&str>( + 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 crate as juniper; + use crate::{EmptyMutation, EmptySubscription}; + + #[test] + fn schema_language() { + struct Query; + #[juniper::graphql_object] + impl Query { + fn blah() -> bool { + true + } + /// This is whatever's description. + fn whatever() -> String { + "foo".to_string() + } + fn fizz(buzz: String) -> Option<&str> { + None + } + fn arr(stuff: Vec) -> Option<&str> { + None + } + #[deprecated] + fn old() -> i32 { + 42 + } + #[deprecated(note = "This field is deprecated, use another.")] + fn really_old() -> f64 { + 42.0 + } + }; + + let schema = crate::RootNode::new( + Query, + EmptyMutation::<()>::new(), + EmptySubscription::<()>::new(), + ); + let ast = graphql_parser::parse_schema::<&str>( + 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..de7da1d9f --- /dev/null +++ b/juniper/src/schema/translate/graphql_parser.rs @@ -0,0 +1,306 @@ +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, Text}; +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 crate::ast::{InputValue, Type}; +use crate::schema::meta::DeprecationStatus; +use crate::schema::meta::{Argument, EnumValue, Field, MetaType}; +use crate::schema::model::SchemaType; +use crate::schema::translate::SchemaTranslator; +use crate::value::ScalarValue; + +pub struct GraphQLParserTranslator; + +impl<'a, S: 'a, T> From<&'a SchemaType<'a, S>> for Document<'a, T> +where + S: ScalarValue, + T: Text<'a> + Default, +{ + fn from(input: &'a SchemaType<'a, S>) -> Document<'a, T> { + GraphQLParserTranslator::translate_schema(input) + } +} + +impl<'a, T> SchemaTranslator<'a, graphql_parser::schema::Document<'a, T>> + for GraphQLParserTranslator +where + T: Text<'a> + Default, +{ + fn translate_schema(input: &'a SchemaType) -> graphql_parser::schema::Document<'a, T> + 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(Definition::TypeDefinition) + .collect(); + doc.definitions.append(&mut types); + + doc.definitions + .push(Definition::SchemaDefinition(SchemaDefinition { + position: Pos::default(), + directives: vec![], + query: Some(From::from(input.query_type_name.as_str())), + mutation: input + .mutation_type_name + .as_ref() + .map(|s| From::from(s.as_str())), + subscription: input + .subscription_type_name + .as_ref() + .map(|s| From::from(s.as_str())), + })); + + doc + } +} + +impl GraphQLParserTranslator { + fn translate_argument<'a, S, T>(input: &'a Argument) -> ExternalInputValue<'a, T> + where + S: ScalarValue, + T: Text<'a>, + { + ExternalInputValue { + position: Pos::default(), + description: input.description.as_ref().map(From::from), + name: From::from(input.name.as_str()), + value_type: GraphQLParserTranslator::translate_type(&input.arg_type), + default_value: input + .default_value + .as_ref() + .map(|x| GraphQLParserTranslator::translate_value(x)), + directives: vec![], + } + } + + fn translate_value<'a, S: 'a, T>(input: &'a InputValue) -> ExternalValue<'a, T> + where + S: ScalarValue, + T: Text<'a>, + { + 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(From::from(x.as_str())), + InputValue::Variable(x) => ExternalValue::Variable(From::from(x.as_str())), + 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( + From::from(name_span.item.as_str()), + GraphQLParserTranslator::translate_value(&value_span.item), + ); + }); + ExternalValue::Object(fields) + } + } + } + + fn translate_type<'a, T>(input: &'a Type<'a>) -> ExternalType<'a, T> + where + T: Text<'a>, + { + match input { + Type::Named(x) => ExternalType::NamedType(From::from(x.as_ref())), + Type::List(x) => { + ExternalType::ListType(GraphQLParserTranslator::translate_type(x).into()) + } + Type::NonNullNamed(x) => { + ExternalType::NonNullType(Box::new(ExternalType::NamedType(From::from(x.as_ref())))) + } + Type::NonNullList(x) => ExternalType::NonNullType(Box::new(ExternalType::ListType( + Box::new(GraphQLParserTranslator::translate_type(x)), + ))), + } + } + + fn translate_meta<'a, S, T>(input: &'a MetaType) -> ExternalTypeDefinition<'a, T> + where + S: ScalarValue, + T: Text<'a>, + { + match input { + MetaType::Scalar(x) => ExternalTypeDefinition::Scalar(ExternalScalarType { + position: Pos::default(), + description: x.description.as_ref().map(From::from), + name: From::from(x.name.as_ref()), + directives: vec![], + }), + MetaType::Enum(x) => ExternalTypeDefinition::Enum(ExternalEnum { + position: Pos::default(), + description: x.description.as_ref().map(|s| From::from(s.as_str())), + name: From::from(x.name.as_ref()), + directives: vec![], + values: x + .values + .iter() + .map(GraphQLParserTranslator::translate_enum_value) + .collect(), + }), + MetaType::Union(x) => ExternalTypeDefinition::Union(ExternalUnionType { + position: Pos::default(), + description: x.description.as_ref().map(|s| From::from(s.as_str())), + name: From::from(x.name.as_ref()), + directives: vec![], + types: x + .of_type_names + .iter() + .map(|s| From::from(s.as_str())) + .collect(), + }), + MetaType::Interface(x) => ExternalTypeDefinition::Interface(ExternalInterfaceType { + position: Pos::default(), + description: x.description.as_ref().map(|s| From::from(s.as_str())), + name: From::from(x.name.as_ref()), + 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.as_ref().map(|s| From::from(s.as_str())), + name: From::from(x.name.as_ref()), + 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.as_ref().map(|s| From::from(s.as_str())), + name: From::from(x.name.as_ref()), + directives: vec![], + fields: x + .fields + .iter() + .filter(|x| !x.is_builtin()) + .map(GraphQLParserTranslator::translate_field) + .collect(), + implements_interfaces: x + .interface_names + .iter() + .map(|s| From::from(s.as_str())) + .collect(), + }), + _ => panic!("unknown meta type when translating"), + } + } + + fn translate_enum_value<'a, T>(input: &'a EnumValue) -> ExternalEnumValue<'a, T> + where + T: Text<'a>, + { + ExternalEnumValue { + position: Pos::default(), + name: From::from(input.name.as_ref()), + description: input.description.as_ref().map(|s| From::from(s.as_str())), + directives: generate_directives(&input.deprecation_status), + } + } + + fn translate_field<'a, S: 'a, T>(input: &'a Field) -> ExternalField<'a, T> + where + S: ScalarValue, + T: Text<'a>, + { + let arguments = input + .arguments + .as_ref() + .map(|a| { + a.iter() + .filter(|x| !x.is_builtin()) + .map(|x| GraphQLParserTranslator::translate_argument(&x)) + .collect() + }) + .unwrap_or_else(|| Vec::new()); + + ExternalField { + position: Pos::default(), + name: From::from(input.name.as_str()), + description: input.description.as_ref().map(|s| From::from(s.as_str())), + directives: generate_directives(&input.deprecation_status), + field_type: GraphQLParserTranslator::translate_type(&input.field_type), + arguments, + } + } +} + +fn deprecation_to_directive<'a, T>(status: &DeprecationStatus) -> Option> +where + T: Text<'a>, +{ + match status { + DeprecationStatus::Current => None, + DeprecationStatus::Deprecated(reason) => Some(ExternalDirective { + position: Pos::default(), + name: From::from("deprecated"), + arguments: if let Some(reason) = reason { + vec![( + From::from("reason"), + 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<'a, T>(status: &DeprecationStatus) -> Vec> +where + T: Text<'a>, +{ + 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..2408af630 --- /dev/null +++ b/juniper/src/schema/translate/mod.rs @@ -0,0 +1,8 @@ +use crate::{ScalarValue, SchemaType}; + +pub trait SchemaTranslator<'a, T> { + fn translate_schema(s: &'a SchemaType) -> T; +} + +#[cfg(feature = "graphql-parser-integration")] +pub mod graphql_parser; From ef0a0c4375f7c260b842f4a8fe2a178a634f6d58 Mon Sep 17 00:00:00 2001 From: Alexander Lyon Date: Fri, 17 Apr 2020 17:00:17 +0100 Subject: [PATCH 2/6] Add an enum to schema language test --- juniper/src/schema/model.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/juniper/src/schema/model.rs b/juniper/src/schema/model.rs index a72ce507f..9f7f8ec5f 100644 --- a/juniper/src/schema/model.rs +++ b/juniper/src/schema/model.rs @@ -597,10 +597,15 @@ mod test { #[cfg(feature = "schema-language")] mod schema_language { use crate as juniper; - use crate::{EmptyMutation, EmptySubscription}; + use crate::{EmptyMutation, EmptySubscription, GraphQLEnum}; #[test] fn schema_language() { + #[derive(GraphQLEnum)] + enum Fruit { + Apple, + Orange, + } struct Query; #[juniper::graphql_object] impl Query { @@ -617,6 +622,9 @@ mod test { fn arr(stuff: Vec) -> Option<&str> { None } + fn fruit() -> Fruit { + Fruit::Apple + } #[deprecated] fn old() -> i32 { 42 @@ -634,12 +642,18 @@ mod test { ); let ast = graphql_parser::parse_schema::<&str>( 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 e42bd8266b45f8b5e84d70bc5d7b98a11f98b2ac Mon Sep 17 00:00:00 2001 From: Alexander Lyon Date: Fri, 17 Apr 2020 17:13:37 +0100 Subject: [PATCH 3/6] Add an interface to schema language test --- juniper/src/schema/model.rs | 52 +++++++++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/juniper/src/schema/model.rs b/juniper/src/schema/model.rs index 9f7f8ec5f..823b9205d 100644 --- a/juniper/src/schema/model.rs +++ b/juniper/src/schema/model.rs @@ -597,15 +597,38 @@ mod test { #[cfg(feature = "schema-language")] mod schema_language { use crate as juniper; - use crate::{EmptyMutation, EmptySubscription, GraphQLEnum}; + use crate::{EmptyMutation, EmptySubscription, GraphQLEnum, GraphQLObject, GraphQLInputObject}; #[test] fn schema_language() { + #[derive(GraphQLObject, Default)] + struct Cake { + fresh: bool, + }; + #[derive(GraphQLObject, Default)] + struct IceCream{ + cold: bool, + }; + enum Sweet { + Cake(Cake), + IceCream(IceCream), + } + juniper::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; #[juniper::graphql_object] impl Query { @@ -616,10 +639,14 @@ mod test { fn whatever() -> String { "foo".to_string() } - fn fizz(buzz: String) -> Option<&str> { - None + fn fizz(buzz: String) -> Option { + if buzz == "whatever" { + Some(Sweet::Cake(Cake::default())) + } else { + Some(Sweet::IceCream(IceCream::default())) + } } - fn arr(stuff: Vec) -> Option<&str> { + fn arr(stuff: Vec) -> Option<&str> { None } fn fruit() -> Fruit { @@ -646,18 +673,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 6369f92b9b4732bbfddf4859d657d15e07e042de Mon Sep 17 00:00:00 2001 From: Christian Legnitto Date: Fri, 8 Mar 2019 16:48:27 -0800 Subject: [PATCH 4/6] 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 823b9205d..9a68eafe7 100644 --- a/juniper/src/schema/model.rs +++ b/juniper/src/schema/model.rs @@ -606,7 +606,7 @@ mod test { fresh: bool, }; #[derive(GraphQLObject, Default)] - struct IceCream{ + struct IceCream { cold: bool, }; enum Sweet { @@ -627,7 +627,7 @@ mod test { #[derive(GraphQLInputObject)] struct Coordinate { latitude: f64, - longitude: f64 + longitude: f64, } struct Query; #[juniper::graphql_object] From f0e986063ccac772138cb86cab3387da859c1f27 Mon Sep 17 00:00:00 2001 From: Christian Legnitto Date: Fri, 8 Mar 2019 16:58:05 -0800 Subject: [PATCH 5/6] 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 9a68eafe7..a6223f0c9 100644 --- a/juniper/src/schema/model.rs +++ b/juniper/src/schema/model.rs @@ -614,6 +614,7 @@ mod test { IceCream(IceCream), } juniper::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 }, @@ -673,7 +674,9 @@ mod test { APPLE ORANGE } - interface Sweet + interface Sweet { + isBrownie: Boolean! + } type Cake { fresh: Boolean! } From 3b2892cd42dd46e3dbae107ee5c502da920f1c5a Mon Sep 17 00:00:00 2001 From: Alexander Lyon Date: Fri, 17 Apr 2020 17:50:07 +0100 Subject: [PATCH 6/6] Add a union to schema language test --- juniper/src/schema/model.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/juniper/src/schema/model.rs b/juniper/src/schema/model.rs index a6223f0c9..f64b36ebd 100644 --- a/juniper/src/schema/model.rs +++ b/juniper/src/schema/model.rs @@ -597,7 +597,10 @@ mod test { #[cfg(feature = "schema-language")] mod schema_language { use crate as juniper; - use crate::{EmptyMutation, EmptySubscription, GraphQLEnum, GraphQLObject, GraphQLInputObject}; + use crate::{ + EmptyMutation, EmptySubscription, GraphQLEnum, GraphQLInputObject, GraphQLObject, + GraphQLUnion, + }; #[test] fn schema_language() { @@ -620,6 +623,11 @@ mod test { &IceCream => match *self { Sweet::IceCream(ref x) => Some(x), _ => None }, } }); + #[derive(GraphQLUnion)] + enum GlutenFree { + Cake(Cake), + IceCream(IceCream), + } #[derive(GraphQLEnum)] enum Fruit { Apple, @@ -653,6 +661,13 @@ mod test { fn fruit() -> Fruit { Fruit::Apple } + fn gluten_free(flavor: String) -> GlutenFree { + if flavor == "savory" { + GlutenFree::Cake(Cake::default()) + } else { + GlutenFree::IceCream(IceCream::default()) + } + } #[deprecated] fn old() -> i32 { 42 @@ -670,6 +685,7 @@ mod test { ); let ast = graphql_parser::parse_schema::<&str>( r#" + union GlutenFree = Cake | IceCream enum Fruit { APPLE ORANGE @@ -690,6 +706,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.") }