Deprecate graphql_object! and replace with impl_object

This commit deprecates the graphql_object macro and replaces
all of it's uses with the new impl_object proc macro.
(Except for the old macro tests).

This commit also adds new integration tests for impl_object.
This commit is contained in:
Christoph Herzog 2019-05-07 10:54:16 +02:00
parent 758f3f7d40
commit a993c16b85
No known key found for this signature in database
GPG key ID: DAFF71D48B493238
27 changed files with 1218 additions and 526 deletions

View file

@ -1,10 +1,27 @@
# master # master
- Refactored all crates to the 2018 edition. [#345](https://github.com/graphql-rust/juniper/pull/345) ### impl_object macro
The `graphql_object!` macro is deprecated and will be removed in the future.
It is replaced by the new [impl_object](https://docs.rs/juniper/latest/juniper/macro.impl_object.html) procedural macro.
[#333](https://github.com/graphql-rust/juniper/pull/333)
### 2018 Edition
All crates were refactored to the Rust 2018 edition.
This should not have any impact on your code, since juniper already was 2018 compatible.
[#345](https://github.com/graphql-rust/juniper/pull/345)
### Other changes
- The minimum required Rust version is now `1.31.0`. - The minimum required Rust version is now `1.31.0`.
- The `ScalarValue` custom derive has been renamed to `GraphQLScalarValue`. - The `ScalarValue` custom derive has been renamed to `GraphQLScalarValue`.
- Added built-in support for the canonical schema introspection query via - Added built-in support for the canonical schema introspection query via
`juniper::introspect()`. [#307](https://github.com/graphql-rust/juniper/issues/307) `juniper::introspect()`.
[#307](https://github.com/graphql-rust/juniper/issues/307)
- Fix introspection query validity - Fix introspection query validity
The DirectiveLocation::InlineFragment had an invalid literal value, The DirectiveLocation::InlineFragment had an invalid literal value,
which broke third party tools like apollo cli. which broke third party tools like apollo cli.

View file

@ -23,7 +23,6 @@ harness = false
path = "benches/bench.rs" path = "benches/bench.rs"
[features] [features]
nightly = []
expose-test-schema = [] expose-test-schema = []
default = [ default = [
"chrono", "chrono",

View file

@ -5,15 +5,16 @@ use crate::value::{DefaultScalarValue, Object, Value};
struct TestType; struct TestType;
graphql_object!(TestType: () |&self| { #[crate::impl_object_internal]
field a() -> &str { impl TestType {
fn a() -> &str {
"a" "a"
} }
field b() -> &str { fn b() -> &str {
"b" "b"
} }
}); }
fn run_variable_query<F>(query: &str, vars: Variables<DefaultScalarValue>, f: F) fn run_variable_query<F>(query: &str, vars: Variables<DefaultScalarValue>, f: F)
where where

View file

@ -17,15 +17,16 @@ enum Color {
} }
struct TestType; struct TestType;
graphql_object!(TestType: () |&self| { #[crate::impl_object_internal]
field to_string(color: Color) -> String { impl TestType {
fn to_string(color: Color) -> String {
format!("Color::{:?}", color) format!("Color::{:?}", color)
} }
field a_color() -> Color { fn a_color() -> Color {
Color::Red Color::Red
} }
}); }
fn run_variable_query<F>(query: &str, vars: Variables<DefaultScalarValue>, f: F) fn run_variable_query<F>(query: &str, vars: Variables<DefaultScalarValue>, f: F)
where where

View file

@ -7,34 +7,57 @@ mod field_execution {
struct DataType; struct DataType;
struct DeepDataType; struct DeepDataType;
graphql_object!(DataType: () |&self| { #[crate::impl_object_internal]
field a() -> &str { "Apple" } impl DataType {
field b() -> &str { "Banana" } fn a() -> &str {
field c() -> &str { "Cookie" } "Apple"
field d() -> &str { "Donut" } }
field e() -> &str { "Egg" } fn b() -> &str {
field f() -> &str { "Fish" } "Banana"
}
fn c() -> &str {
"Cookie"
}
fn d() -> &str {
"Donut"
}
fn e() -> &str {
"Egg"
}
fn f() -> &str {
"Fish"
}
field pic(size: Option<i32>) -> String { fn pic(size: Option<i32>) -> String {
format!("Pic of size: {}", size.unwrap_or(50)) format!("Pic of size: {}", size.unwrap_or(50))
} }
field deep() -> DeepDataType { fn deep() -> DeepDataType {
DeepDataType DeepDataType
} }
}); }
graphql_object!(DeepDataType: () |&self| { #[crate::impl_object_internal]
field a() -> &str { "Already Been Done" } impl DeepDataType {
field b() -> &str { "Boring" } fn a() -> &str {
field c() -> Vec<Option<&str>> { vec![Some("Contrived"), None, Some("Confusing")] } "Already Been Done"
}
fn b() -> &str {
"Boring"
}
fn c() -> Vec<Option<&str>> {
vec![Some("Contrived"), None, Some("Confusing")]
}
field deeper() -> Vec<Option<DataType>> { vec![Some(DataType), None, Some(DataType) ] } fn deeper() -> Vec<Option<DataType>> {
}); vec![Some(DataType), None, Some(DataType)]
}
}
#[test] #[test]
fn test() { fn test() {
let schema = RootNode::new(DataType, EmptyMutation::<()>::new()); let schema =
RootNode::<_, _, crate::DefaultScalarValue>::new(DataType, EmptyMutation::<()>::new());
let doc = r" let doc = r"
query Example($size: Int) { query Example($size: Int) {
a, a,
@ -139,12 +162,21 @@ mod merge_parallel_fragments {
struct Type; struct Type;
graphql_object!(Type: () |&self| { #[crate::impl_object_internal]
field a() -> &str { "Apple" } impl Type {
field b() -> &str { "Banana" } fn a() -> &str {
field c() -> &str { "Cherry" } "Apple"
field deep() -> Type { Type } }
}); fn b() -> &str {
"Banana"
}
fn c() -> &str {
"Cherry"
}
fn deep() -> Type {
Type
}
}
#[test] #[test]
fn test() { fn test() {
@ -214,21 +246,43 @@ mod merge_parallel_inline_fragments {
struct Type; struct Type;
struct Other; struct Other;
graphql_object!(Type: () |&self| { #[crate::impl_object_internal]
field a() -> &str { "Apple" } impl Type {
field b() -> &str { "Banana" } fn a() -> &str {
field c() -> &str { "Cherry" } "Apple"
field deep() -> Type { Type } }
field other() -> Vec<Other> { vec![Other, Other] } fn b() -> &str {
}); "Banana"
}
fn c() -> &str {
"Cherry"
}
fn deep() -> Type {
Type
}
fn other() -> Vec<Other> {
vec![Other, Other]
}
}
graphql_object!(Other: () |&self| { #[crate::impl_object_internal]
field a() -> &str { "Apple" } impl Other {
field b() -> &str { "Banana" } fn a() -> &str {
field c() -> &str { "Cherry" } "Apple"
field deep() -> Type { Type } }
field other() -> Vec<Other> { vec![Other, Other] } fn b() -> &str {
}); "Banana"
}
fn c() -> &str {
"Cherry"
}
fn deep() -> Type {
Type
}
fn other() -> Vec<Other> {
vec![Other, Other]
}
}
#[test] #[test]
fn test() { fn test() {
@ -342,9 +396,14 @@ mod threads_context_correctly {
impl Context for TestContext {} impl Context for TestContext {}
graphql_object!(Schema: TestContext |&self| { #[crate::impl_object_internal(
field a(&executor) -> String { executor.context().value.clone() } Context = TestContext,
}); )]
impl Schema {
fn a(context: &TestContext) -> String {
context.value.clone()
}
}
#[test] #[test]
fn test() { fn test() {
@ -403,36 +462,42 @@ mod dynamic_context_switching {
struct ItemRef; struct ItemRef;
graphql_object!(Schema: OuterContext |&self| { #[crate::impl_object_internal(Context = OuterContext)]
field item_opt(&executor, key: i32) -> Option<(&InnerContext, ItemRef)> { impl Schema {
fn item_opt(context: &OuterContext, key: i32) -> Option<(&InnerContext, ItemRef)> {
executor.context().items.get(&key).map(|c| (c, ItemRef)) executor.context().items.get(&key).map(|c| (c, ItemRef))
} }
field item_res(&executor, key: i32) -> FieldResult<(&InnerContext, ItemRef)> { fn item_res(context: &OuterContext, key: i32) -> FieldResult<(&InnerContext, ItemRef)> {
let res = executor.context().items.get(&key) let res = context
.items
.get(&key)
.ok_or(format!("Could not find key {}", key)) .ok_or(format!("Could not find key {}", key))
.map(|c| (c, ItemRef))?; .map(|c| (c, ItemRef))?;
Ok(res) Ok(res)
} }
field item_res_opt(&executor, key: i32) -> FieldResult<Option<(&InnerContext, ItemRef)>> { fn item_res_opt(
context: &OuterContext,
key: i32,
) -> FieldResult<Option<(&InnerContext, ItemRef)>> {
if key > 100 { if key > 100 {
Err(format!("Key too large: {}", key))?; Err(format!("Key too large: {}", key))?;
} }
Ok(executor.context().items.get(&key) Ok(context.items.get(&key).map(|c| (c, ItemRef)))
.map(|c| (c, ItemRef)))
} }
field item_always(&executor, key: i32) -> (&InnerContext, ItemRef) { fn item_always(context: &OuterContext, key: i32) -> (&InnerContext, ItemRef) {
executor.context().items.get(&key) context.items.get(&key).map(|c| (c, ItemRef)).unwrap()
.map(|c| (c, ItemRef))
.unwrap()
} }
}); }
graphql_object!(ItemRef: InnerContext |&self| { #[crate::impl_object_internal(Context = InnerContext)]
field value(&executor) -> String { executor.context().value.clone() } impl ItemRef {
}); fn value(context: &InnerContext) -> String {
context.value.clone()
}
}
#[test] #[test]
fn test_opt() { fn test_opt() {
@ -736,19 +801,37 @@ mod propagates_errors_to_nullable_fields {
} }
} }
graphql_object!(Schema: () |&self| { #[crate::impl_object_internal]
field inner() -> Inner { Inner } impl Schema {
field inners() -> Vec<Inner> { (0..5).map(|_| Inner).collect() } fn inner() -> Inner {
field nullable_inners() -> Vec<Option<Inner>> { (0..5).map(|_| Some(Inner)).collect() } Inner
}); }
fn inners() -> Vec<Inner> {
(0..5).map(|_| Inner).collect()
}
fn nullable_inners() -> Vec<Option<Inner>> {
(0..5).map(|_| Some(Inner)).collect()
}
}
graphql_object!(Inner: () |&self| { #[crate::impl_object_internal]
field nullable_field() -> Option<Inner> { Some(Inner) } impl Inner {
field non_nullable_field() -> Inner { Inner } fn nullable_field() -> Option<Inner> {
field nullable_error_field() -> FieldResult<Option<&str>> { Err("Error for nullableErrorField")? } Some(Inner)
field non_nullable_error_field() -> FieldResult<&str> { Err("Error for nonNullableErrorField")? } }
field custom_error_field() -> Result<&str, CustomError> { Err(CustomError::NotFound) } fn non_nullable_field() -> Inner {
}); Inner
}
fn nullable_error_field() -> FieldResult<Option<&str>> {
Err("Error for nullableErrorField")?
}
fn non_nullable_error_field() -> FieldResult<&str> {
Err("Error for nonNullableErrorField")?
}
fn custom_error_field() -> Result<&str, CustomError> {
Err(CustomError::NotFound)
}
}
#[test] #[test]
fn nullable_first_level() { fn nullable_first_level() {
@ -985,13 +1068,17 @@ mod named_operations {
struct Schema; struct Schema;
graphql_object!(Schema: () |&self| { #[crate::impl_object_internal]
field a() -> &str { "b" } impl Schema {
}); fn a() -> &str {
"b"
}
}
#[test] #[test]
fn uses_inline_operation_if_no_name_provided() { fn uses_inline_operation_if_no_name_provided() {
let schema = RootNode::new(Schema, EmptyMutation::<()>::new()); let schema =
RootNode::<_, _, crate::DefaultScalarValue>::new(Schema, EmptyMutation::<()>::new());
let doc = r"{ a }"; let doc = r"{ a }";
let vars = vec![].into_iter().collect(); let vars = vec![].into_iter().collect();

View file

@ -37,12 +37,17 @@ mod interface {
} }
} }
graphql_object!(Dog: () |&self| { #[crate::impl_object_internal(
field name() -> &str { &self.name } interfaces = [&Pet]
field woofs() -> bool { self.woofs } )]
impl Dog {
interfaces: [&Pet] fn name(&self) -> &str {
}); &self.name
}
fn woofs(&self) -> bool {
self.woofs
}
}
struct Cat { struct Cat {
name: String, name: String,
@ -58,22 +63,28 @@ mod interface {
} }
} }
graphql_object!(Cat: () |&self| { #[crate::impl_object_internal(
field name() -> &str { &self.name } interfaces = [&Pet]
field meows() -> bool { self.meows } )]
impl Cat {
interfaces: [&Pet] fn name(&self) -> &str {
}); &self.name
}
fn meows(&self) -> bool {
self.meows
}
}
struct Schema { struct Schema {
pets: Vec<Box<Pet>>, pets: Vec<Box<Pet>>,
} }
graphql_object!(Schema: () |&self| { #[crate::impl_object_internal]
field pets() -> Vec<&Pet> { impl Schema {
fn pets(&self) -> Vec<&Pet> {
self.pets.iter().map(|p| p.as_ref()).collect() self.pets.iter().map(|p| p.as_ref()).collect()
} }
}); }
#[test] #[test]
fn test() { fn test() {
@ -177,10 +188,15 @@ mod union {
} }
} }
graphql_object!(Dog: () |&self| { #[crate::impl_object_internal]
field name() -> &str { &self.name } impl Dog {
field woofs() -> bool { self.woofs } fn name(&self) -> &str {
}); &self.name
}
fn woofs(&self) -> bool {
self.woofs
}
}
struct Cat { struct Cat {
name: String, name: String,
@ -193,20 +209,26 @@ mod union {
} }
} }
graphql_object!(Cat: () |&self| { #[crate::impl_object_internal]
field name() -> &str { &self.name } impl Cat {
field meows() -> bool { self.meows } fn name(&self) -> &str {
}); &self.name
}
fn meows(&self) -> bool {
self.meows
}
}
struct Schema { struct Schema {
pets: Vec<Box<Pet>>, pets: Vec<Box<Pet>>,
} }
graphql_object!(Schema: () |&self| { #[crate::impl_object_internal]
field pets() -> Vec<&Pet> { impl Schema {
fn pets(&self) -> Vec<&Pet> {
self.pets.iter().map(|p| p.as_ref()).collect() self.pets.iter().map(|p| p.as_ref()).collect()
} }
}); }
#[test] #[test]
fn test() { fn test() {

View file

@ -64,14 +64,27 @@ enum EnumDeprecation {
struct Root; struct Root;
graphql_object!(Root: () |&self| { #[crate::impl_object_internal]
field default_name() -> DefaultName { DefaultName::Foo } impl Root {
field named() -> Named { Named::Foo } fn default_name() -> DefaultName {
field no_trailing_comma() -> NoTrailingComma { NoTrailingComma::Foo } DefaultName::Foo
field enum_description() -> EnumDescription { EnumDescription::Foo } }
field enum_value_description() -> EnumValueDescription { EnumValueDescription::Foo } fn named() -> Named {
field enum_deprecation() -> EnumDeprecation { EnumDeprecation::Foo } Named::Foo
}); }
fn no_trailing_comma() -> NoTrailingComma {
NoTrailingComma::Foo
}
fn enum_description() -> EnumDescription {
EnumDescription::Foo
}
fn enum_value_description() -> EnumValueDescription {
EnumValueDescription::Foo
}
fn enum_deprecation() -> EnumDeprecation {
EnumDeprecation::Foo
}
}
fn run_type_info_query<F>(doc: &str, f: F) fn run_type_info_query<F>(doc: &str, f: F)
where where

View file

@ -79,8 +79,9 @@ struct FieldWithDefaults {
field_two: i32, field_two: i32,
} }
graphql_object!(Root: () |&self| { #[crate::impl_object_internal]
field test_field( impl Root {
fn test_field(
a1: DefaultName, a1: DefaultName,
a2: NoTrailingComma, a2: NoTrailingComma,
a3: Derive, a3: Derive,
@ -95,7 +96,7 @@ graphql_object!(Root: () |&self| {
) -> i32 { ) -> i32 {
0 0
} }
}); }
fn run_type_info_query<F>(doc: &str, f: F) fn run_type_info_query<F>(doc: &str, f: F)
where where

View file

@ -51,22 +51,26 @@ graphql_interface!(Interface: () as "SampleInterface" |&self| {
} }
}); });
graphql_object!(Root: () |&self| { /// The root query object in the schema
description: "The root query object in the schema" #[crate::impl_object_internal(
interfaces = [&Interface]
interfaces: [Interface] Scalar = crate::DefaultScalarValue,
)]
field sample_enum() -> Sample { impl Root {
fn sample_enum() -> Sample {
Sample::One Sample::One
} }
field sample_scalar( #[graphql(arguments(
first: i32 as "The first number", first(description = "The first number",),
second = 123: i32 as "The second number" second(description = "The second number", default = 123,),
) -> Scalar as "A sample scalar field on the object" { ))]
/// A sample scalar field on the object
fn sample_scalar(first: i32, second: i32) -> Scalar {
Scalar(first + second) Scalar(first + second)
} }
}); }
#[test] #[test]
fn test_execution() { fn test_execution() {

View file

@ -62,59 +62,67 @@ struct InputWithDefaults {
a: i32, a: i32,
} }
graphql_object!(TestType: () |&self| { #[crate::impl_object_internal]
field field_with_object_input(input: Option<TestInputObject>) -> String { impl TestType {
fn field_with_object_input(input: Option<TestInputObject>) -> String {
format!("{:?}", input) format!("{:?}", input)
} }
field field_with_nullable_string_input(input: Option<String>) -> String { fn field_with_nullable_string_input(input: Option<String>) -> String {
format!("{:?}", input) format!("{:?}", input)
} }
field field_with_non_nullable_string_input(input: String) -> String { fn field_with_non_nullable_string_input(input: String) -> String {
format!("{:?}", input) format!("{:?}", input)
} }
field field_with_default_argument_value(input = ("Hello World".to_owned()): String) -> String { #[graphql(
arguments(
input(
default = "Hello World".to_string(),
)
)
)]
fn field_with_default_argument_value(input: String) -> String {
format!("{:?}", input) format!("{:?}", input)
} }
field field_with_nested_object_input(input: Option<TestNestedInputObject>) -> String { fn field_with_nested_object_input(input: Option<TestNestedInputObject>) -> String {
format!("{:?}", input) format!("{:?}", input)
} }
field list(input: Option<Vec<Option<String>>>) -> String { fn list(input: Option<Vec<Option<String>>>) -> String {
format!("{:?}", input) format!("{:?}", input)
} }
field nn_list(input: Vec<Option<String>>) -> String { fn nn_list(input: Vec<Option<String>>) -> String {
format!("{:?}", input) format!("{:?}", input)
} }
field list_nn(input: Option<Vec<String>>) -> String { fn list_nn(input: Option<Vec<String>>) -> String {
format!("{:?}", input) format!("{:?}", input)
} }
field nn_list_nn(input: Vec<String>) -> String { fn nn_list_nn(input: Vec<String>) -> String {
format!("{:?}", input) format!("{:?}", input)
} }
field example_input(arg: ExampleInputObject) -> String { fn example_input(arg: ExampleInputObject) -> String {
format!("a: {:?}, b: {:?}", arg.a, arg.b) format!("a: {:?}, b: {:?}", arg.a, arg.b)
} }
field input_with_defaults(arg: InputWithDefaults) -> String { fn input_with_defaults(arg: InputWithDefaults) -> String {
format!("a: {:?}", arg.a) format!("a: {:?}", arg.a)
} }
field integer_input(value: i32) -> String { fn integer_input(value: i32) -> String {
format!("value: {}", value) format!("value: {}", value)
} }
field float_input(value: f64) -> String { fn float_input(value: f64) -> String {
format!("value: {}", value) format!("value: {}", value)
} }
}); }
fn run_variable_query<F>(query: &str, vars: Variables<DefaultScalarValue>, f: F) fn run_variable_query<F>(query: &str, vars: Variables<DefaultScalarValue>, f: F)
where where

View file

@ -209,20 +209,22 @@ mod integration_test {
#[test] #[test]
fn test_serialization() { fn test_serialization() {
struct Root; struct Root;
graphql_object!(Root: () |&self| {
field exampleNaiveDate() -> NaiveDate { #[crate::impl_object_internal]
impl Root {
fn exampleNaiveDate() -> NaiveDate {
NaiveDate::from_ymd(2015, 3, 14) NaiveDate::from_ymd(2015, 3, 14)
} }
field exampleNaiveDateTime() -> NaiveDateTime { fn exampleNaiveDateTime() -> NaiveDateTime {
NaiveDate::from_ymd(2016, 7, 8).and_hms(9, 10, 11) NaiveDate::from_ymd(2016, 7, 8).and_hms(9, 10, 11)
} }
field exampleDateTimeFixedOffset() -> DateTime<FixedOffset> { fn exampleDateTimeFixedOffset() -> DateTime<FixedOffset> {
DateTime::parse_from_rfc3339("1996-12-19T16:39:57-08:00").unwrap() DateTime::parse_from_rfc3339("1996-12-19T16:39:57-08:00").unwrap()
} }
field exampleDateTimeUtc() -> DateTime<Utc> { fn exampleDateTimeUtc() -> DateTime<Utc> {
Utc.timestamp(61, 0) Utc.timestamp(61, 0)
} }
}); }
let doc = r#" let doc = r#"
{ {

View file

@ -61,13 +61,18 @@ impl Character for Droid {
fn id(&self) -> &str { &self.id } fn id(&self) -> &str { &self.id }
} }
juniper::graphql_object!(Human: Database as "Human" |&self| { #[juniper::impl_object(Context = Database)]
field id() -> &str { &self.id } impl Human {
}); fn id(&self) -> &str { &self.id }
}
juniper::graphql_object!(Droid: Database as "Droid" |&self| { #[juniper::impl_object(
field id() -> &str { &self.id } name = "Droid",
}); Context = Database,
)]
impl Droid {
fn id(&self) -> &str { &self.id }
}
// You can introduce lifetimes or generic parameters by < > before the name. // You can introduce lifetimes or generic parameters by < > before the name.
juniper::graphql_interface!(<'a> &'a Character: Database as "Character" |&self| { juniper::graphql_interface!(<'a> &'a Character: Database as "Character" |&self| {

View file

@ -1,6 +1,12 @@
/** /**
## DEPRECATION WARNING
The `graphql_object!` macro is deprecated and will be removed soon.
Use the new[impl_object](https://docs.rs/juniper/latest/juniper/macro.impl_object.html) macro instead.
Expose GraphQL objects Expose GraphQL objects
This is a short-hand macro that implements the `GraphQLType` trait for a given This is a short-hand macro that implements the `GraphQLType` trait for a given
type. By using this macro instead of implementing it manually, you gain type type. By using this macro instead of implementing it manually, you gain type
safety and reduce repetitive declarations. safety and reduce repetitive declarations.
@ -308,7 +314,6 @@ arg_name: ArgType
``` ```
[1]: struct.Executor.html [1]: struct.Executor.html
*/ */
#[macro_export] #[macro_export]
macro_rules! graphql_object { macro_rules! graphql_object {

View file

@ -26,63 +26,111 @@ struct Point {
x: i32, x: i32,
} }
graphql_object!(Root: () |&self| { #[crate::impl_object_internal]
field simple() -> i32 { 0 } impl Root {
field exec_arg(&executor) -> i32 { 0 } fn simple() -> i32 {
field exec_arg_and_more(&executor, arg: i32) -> i32 { 0 } 0
}
fn exec_arg(executor: &Executor) -> i32 {
0
}
fn exec_arg_and_more(executor: &Executor, arg: i32) -> i32 {
0
}
field single_arg(arg: i32) -> i32 { 0 } fn single_arg(arg: i32) -> i32 {
field multi_args( 0
arg1: i32, }
arg2: i32
) -> i32 { 0 }
field multi_args_trailing_comma(
arg1: i32,
arg2: i32,
) -> i32 { 0 }
field single_arg_descr(arg: i32 as "The arg") -> i32 { 0 } fn multi_args(arg1: i32, arg2: i32) -> i32 {
field multi_args_descr( 0
arg1: i32 as "The first arg", }
arg2: i32 as "The second arg"
) -> i32 { 0 }
field multi_args_descr_trailing_comma(
arg1: i32 as "The first arg",
arg2: i32 as "The second arg",
) -> i32 { 0 }
field attr_arg_descr(#[doc = "The arg"] arg: i32) -> i32 { 0 } fn multi_args_trailing_comma(arg1: i32, arg2: i32) -> i32 {
field attr_arg_descr_collapse( 0
#[doc = "The arg"] }
#[doc = "and more details"]
arg: i32,
) -> i32 { 0 }
field arg_with_default(arg = 123: i32) -> i32 { 0 } #[graphql(arguments(arg(description = "The arg")))]
field multi_args_with_default( fn single_arg_descr(arg: i32) -> i32 {
arg1 = 123: i32, 0
arg2 = 456: i32 }
) -> i32 { 0 }
field multi_args_with_default_trailing_comma(
arg1 = 123: i32,
arg2 = 456: i32,
) -> i32 { 0 }
field arg_with_default_descr(arg = 123: i32 as "The arg") -> i32 { 0 } #[graphql(arguments(
field multi_args_with_default_descr( arg1(description = "The first arg",),
arg1 = 123: i32 as "The first arg", arg2(description = "The second arg")
arg2 = 456: i32 as "The second arg" ))]
) -> i32 { 0 } fn multi_args_descr(arg1: i32, arg2: i32) -> i32 {
field multi_args_with_default_trailing_comma_descr( 0
arg1 = 123: i32 as "The first arg", }
arg2 = 456: i32 as "The second arg",
) -> i32 { 0 }
field args_with_complex_default( #[graphql(arguments(
arg1 = ("test".to_owned()): String as "A string default argument", arg1(description = "The first arg",),
arg2 = (Point { x: 1 }): Point as "An input object default argument", arg2(description = "The second arg")
) -> i32 { 0 } ))]
}); fn multi_args_descr_trailing_comma(arg1: i32, arg2: i32) -> i32 {
0
}
// TODO: enable once [RFC 2565](https://github.com/rust-lang/rust/issues/60406) is implemented
// fn attr_arg_descr(#[doc = "The arg"] arg: i32) -> i32 { 0 }
// fn attr_arg_descr_collapse(
// #[doc = "The arg"]
// #[doc = "and more details"]
// arg: i32,
// ) -> i32 { 0 }
#[graphql(arguments(arg(default = 123,),))]
fn arg_with_default(arg: i32) -> i32 {
0
}
#[graphql(arguments(arg1(default = 123,), arg2(default = 456,)))]
fn multi_args_with_default(arg1: i32, arg2: i32) -> i32 {
0
}
#[graphql(arguments(arg1(default = 123,), arg2(default = 456,),))]
fn multi_args_with_default_trailing_comma(arg1: i32, arg2: i32) -> i32 {
0
}
#[graphql(arguments(arg(default = 123, description = "The arg")))]
fn arg_with_default_descr(arg: i32) -> i32 {
0
}
#[graphql(arguments(
arg1(default = 123, description = "The first arg"),
arg2(default = 456, description = "The second arg")
))]
fn multi_args_with_default_descr(arg1: i32, arg2: i32) -> i32 {
0
}
#[graphql(arguments(
arg1(default = 123, description = "The first arg",),
arg2(default = 456, description = "The second arg",)
))]
fn multi_args_with_default_trailing_comma_descr(arg1: i32, arg2: i32) -> i32 {
0
}
#[graphql(
arguments(
arg1(
default = "test".to_string(),
description = "A string default argument",
),
arg2(
default = Point{ x: 1 },
description = "An input object default argument",
)
),
)]
fn args_with_complex_default(arg1: String, arg2: Point) -> i32 {
0
}
}
fn run_args_info_query<F>(field_name: &str, f: F) fn run_args_info_query<F>(field_name: &str, f: F)
where where
@ -509,71 +557,73 @@ fn introspect_field_multi_args_descr_trailing_comma() {
}); });
} }
#[test] // TODO: enable once [RFC 2565](https://github.com/rust-lang/rust/issues/60406) is implemented
fn introspect_field_attr_arg_descr() { // #[test]
run_args_info_query("attrArgDescr", |args| { // fn introspect_field_attr_arg_descr() {
assert_eq!(args.len(), 1); // run_args_info_query("attrArgDescr", |args| {
// assert_eq!(args.len(), 1);
assert!(args.contains(&Value::object( // assert!(args.contains(&Value::object(
vec![ // vec![
("name", Value::scalar("arg")), // ("name", Value::scalar("arg")),
("description", Value::scalar("The arg")), // ("description", Value::scalar("The arg")),
("defaultValue", Value::null()), // ("defaultValue", Value::null()),
( // (
"type", // "type",
Value::object( // Value::object(
vec![ // vec![
("name", Value::null()), // ("name", Value::null()),
( // (
"ofType", // "ofType",
Value::object( // Value::object(
vec![("name", Value::scalar("Int"))].into_iter().collect(), // vec![("name", Value::scalar("Int"))].into_iter().collect(),
), // ),
), // ),
] // ]
.into_iter() // .into_iter()
.collect(), // .collect(),
), // ),
), // ),
] // ]
.into_iter() // .into_iter()
.collect(), // .collect(),
))); // )));
}); // });
} // }
#[test] // TODO: enable once [RFC 2565](https://github.com/rust-lang/rust/issues/60406) is implemented
fn introspect_field_attr_arg_descr_collapse() { // #[test]
run_args_info_query("attrArgDescrCollapse", |args| { // fn introspect_field_attr_arg_descr_collapse() {
assert_eq!(args.len(), 1); // run_args_info_query("attrArgDescrCollapse", |args| {
// assert_eq!(args.len(), 1);
assert!(args.contains(&Value::object( // assert!(args.contains(&Value::object(
vec![ // vec![
("name", Value::scalar("arg")), // ("name", Value::scalar("arg")),
("description", Value::scalar("The arg\nand more details")), // ("description", Value::scalar("The arg\nand more details")),
("defaultValue", Value::null()), // ("defaultValue", Value::null()),
( // (
"type", // "type",
Value::object( // Value::object(
vec![ // vec![
("name", Value::null()), // ("name", Value::null()),
( // (
"ofType", // "ofType",
Value::object( // Value::object(
vec![("name", Value::scalar("Int"))].into_iter().collect(), // vec![("name", Value::scalar("Int"))].into_iter().collect(),
), // ),
), // ),
] // ]
.into_iter() // .into_iter()
.collect(), // .collect(),
), // ),
), // ),
] // ]
.into_iter() // .into_iter()
.collect(), // .collect(),
))); // )));
}); // });
} // }
#[test] #[test]
fn introspect_field_arg_with_default() { fn introspect_field_arg_with_default() {

View file

@ -20,49 +20,77 @@ Syntax to validate:
*/ */
graphql_object!(Root: () |&self| { #[crate::impl_object_internal(
field simple() -> i32 { 0 } interfaces = [&Interface],
)]
field description() -> i32 as "Field description" { 0 } impl Root {
fn simple() -> i32 {
field deprecated "Deprecation reason" 0
deprecated() -> i32 { 0 } }
field deprecated "Deprecation reason"
deprecated_descr() -> i32 as "Field description" { 0 }
/// Field description /// Field description
field attr_description() -> i32 { 0 } fn description() -> i32 {
0
}
#[graphql(deprecated = "Deprecation reason")]
fn deprecated() -> i32 {
0
}
#[graphql(deprecated = "Deprecation reason", description = "Field description")]
fn deprecated_descr() -> i32 {
0
}
/// Field description
fn attr_description() -> i32 {
0
}
/// Field description /// Field description
/// with `collapse_docs` behavior /// with `collapse_docs` behavior
field attr_description_collapse() -> i32 { 0 } fn attr_description_collapse() -> i32 {
0
}
/// Get the i32 representation of 0. /// Get the i32 representation of 0.
/// ///
/// - This comment is longer. /// - This comment is longer.
/// - These two lines are rendered as bullets by GraphiQL. /// - These two lines are rendered as bullets by GraphiQL.
/// - subsection /// - subsection
field attr_description_long() -> i32 { 0 } fn attr_description_long() -> i32 {
0
}
#[deprecated] #[graphql(deprecated)]
field attr_deprecated() -> i32 { 0 } fn attr_deprecated() -> i32 {
0
}
#[deprecated(note = "Deprecation reason")] #[graphql(deprecated = "Deprecation reason")]
field attr_deprecated_reason() -> i32 { 0 } fn attr_deprecated_reason() -> i32 {
0
}
/// Field description /// Field description
#[deprecated(note = "Deprecation reason")] #[graphql(deprecated = "Deprecation reason")]
field attr_deprecated_descr() -> i32 { 0 } fn attr_deprecated_descr() -> i32 {
0
}
field with_field_result() -> FieldResult<i32> { Ok(0) } fn with_field_result() -> FieldResult<i32> {
Ok(0)
}
field with_return() -> i32 { return 0; } fn with_return() -> i32 {
return 0;
}
field with_return_field_result() -> FieldResult<i32> { return Ok(0); } fn with_return_field_result() -> FieldResult<i32> {
return Ok(0);
interfaces: [Interface] }
}); }
graphql_interface!(Interface: () |&self| { graphql_interface!(Interface: () |&self| {
field simple() -> i32 { 0 } field simple() -> i32 { 0 }

View file

@ -0,0 +1,269 @@
use super::util;
use crate::{graphql_value, EmptyMutation, RootNode};
#[derive(Default)]
struct Context {
flag1: bool,
}
impl crate::Context for Context {}
struct WithLifetime<'a> {
value: &'a str,
}
#[crate::impl_object_internal(Context=Context)]
impl<'a> WithLifetime<'a> {
fn value(&'a self) -> &'a str {
self.value
}
}
struct WithContext;
#[crate::impl_object_internal(Context=Context)]
impl WithContext {
fn ctx(ctx: &Context) -> bool {
ctx.flag1
}
}
#[derive(Default)]
struct Query {
b: bool,
}
#[crate::impl_object_internal(
scalar = crate::DefaultScalarValue,
name = "Query",
context = Context,
)]
/// Query Description.
impl<'a> Query {
#[graphql(description = "With Self Description")]
fn with_self(&self) -> bool {
self.b
}
fn independent() -> i32 {
100
}
fn with_executor(_exec: &Executor<Context>) -> bool {
true
}
fn with_executor_and_self(&self, _exec: &Executor<Context>) -> bool {
true
}
fn with_context(_context: &Context) -> bool {
true
}
fn with_context_and_self(&self, _context: &Context) -> bool {
true
}
#[graphql(name = "renamed")]
fn has_custom_name() -> bool {
true
}
#[graphql(description = "attr")]
fn has_description_attr() -> bool {
true
}
/// Doc description
fn has_description_doc_comment() -> bool {
true
}
fn has_argument(arg1: bool) -> bool {
arg1
}
#[graphql(arguments(default_arg(default = true)))]
fn default_argument(default_arg: bool) -> bool {
default_arg
}
#[graphql(arguments(arg(description = "my argument description")))]
fn arg_with_description(arg: bool) -> bool {
arg
}
fn with_context_child(&self) -> WithContext {
WithContext
}
fn with_lifetime_child(&self) -> WithLifetime<'a> {
WithLifetime { value: "blub" }
}
}
#[derive(Default)]
struct Mutation;
#[crate::impl_object_internal(context = Context)]
impl Mutation {
fn empty() -> bool {
true
}
}
#[test]
fn impl_object_introspect() {
let res = util::run_info_query::<Query, Mutation, Context>("Query");
assert_eq!(
res,
crate::graphql_value!({
"name": "Query",
"description": "Query Description.",
"fields": [
{
"name": "withSelf",
"description": "With Self Description",
"args": [],
},
{
"name": "independent",
"description": None,
"args": [],
},
{
"name": "withExecutor",
"description": None,
"args": [],
},
{
"name": "withExecutorAndSelf",
"description": None,
"args": [],
},
{
"name": "withContext",
"description": None,
"args": [],
},
{
"name": "withContextAndSelf",
"description": None,
"args": [],
},
{
"name": "renamed",
"description": None,
"args": [],
},
{
"name": "hasDescriptionAttr",
"description": "attr",
"args": [],
},
{
"name": "hasDescriptionDocComment",
"description": "Doc description",
"args": [],
},
{
"name": "hasArgument",
"description": None,
"args": [
{
"name": "arg1",
"description": None,
"type": {
"name": None,
},
}
],
},
{
"name": "defaultArgument",
"description": None,
"args": [
{
"name": "defaultArg",
"description": None,
"type": {
"name": "Boolean",
},
}
],
},
{
"name": "argWithDescription",
"description": None,
"args": [
{
"name": "arg",
"description": "my argument description",
"type": {
"name": None
},
}
],
},
{
"name": "withContextChild",
"description": None,
"args": [],
},
{
"name": "withLifetimeChild",
"description": None,
"args": [],
},
]
})
);
}
#[test]
fn impl_object_query() {
let doc = r#"
query {
withSelf
independent
withExecutor
withExecutorAndSelf
withContext
withContextAndSelf
renamed
hasArgument(arg1: true)
defaultArgument
argWithDescription(arg: true)
withContextChild {
ctx
}
withLifetimeChild {
value
}
}
"#;
let schema = RootNode::new(Query { b: true }, EmptyMutation::<Context>::new());
let vars = std::collections::HashMap::new();
let (result, errs) = crate::execute(doc, None, &schema, &vars, &Context { flag1: true })
.expect("Execution failed");
assert_eq!(errs, []);
assert_eq!(
result,
graphql_value!({
"withSelf": true,
"independent": 100,
"withExecutor": true,
"withExecutorAndSelf": true,
"withContext": true,
"withContextAndSelf": true,
"renamed": true,
"hasArgument": true,
"defaultArgument": true,
"argWithDescription": true,
"withContextChild": { "ctx": true },
"withLifetimeChild": { "value": "blub" },
})
);
}

View file

@ -42,9 +42,12 @@ struct ResolversWithTrailingComma;
struct Root; struct Root;
graphql_object!(Concrete: () |&self| { #[crate::impl_object_internal]
field simple() -> i32 { 0 } impl Concrete {
}); fn simple() -> i32 {
0
}
}
graphql_interface!(CustomName: () as "ACustomNamedInterface" |&self| { graphql_interface!(CustomName: () as "ACustomNamedInterface" |&self| {
field simple() -> i32 { 0 } field simple() -> i32 { 0 }
@ -108,24 +111,40 @@ graphql_interface!(ResolversWithTrailingComma: () |&self| {
field simple() -> i32 { 0 } field simple() -> i32 { 0 }
}); });
graphql_object!(<'a> Root: () as "Root" |&self| { #[crate::impl_object_internal]
field custom_name() -> CustomName { CustomName {} } impl<'a> Root {
fn custom_name() -> CustomName {
field with_lifetime() -> WithLifetime<'a> { WithLifetime { data: PhantomData } } CustomName {}
field with_generics() -> WithGenerics<i32> { WithGenerics { data: 123 } }
field description_first() -> DescriptionFirst { DescriptionFirst {} }
field fields_first() -> FieldsFirst { FieldsFirst {} }
field interfaces_first() -> InterfacesFirst { InterfacesFirst {} }
field commas_with_trailing() -> CommasWithTrailing { CommasWithTrailing {} }
field commas_on_meta() -> CommasOnMeta { CommasOnMeta {} }
field resolvers_with_trailing_comma() -> ResolversWithTrailingComma {
ResolversWithTrailingComma {}
} }
}); fn with_lifetime() -> WithLifetime<'a> {
WithLifetime { data: PhantomData }
}
fn with_generics() -> WithGenerics<i32> {
WithGenerics { data: 123 }
}
fn description_first() -> DescriptionFirst {
DescriptionFirst {}
}
fn fields_first() -> FieldsFirst {
FieldsFirst {}
}
fn interfaces_first() -> InterfacesFirst {
InterfacesFirst {}
}
fn commas_with_trailing() -> CommasWithTrailing {
CommasWithTrailing {}
}
fn commas_on_meta() -> CommasOnMeta {
CommasOnMeta {}
}
fn resolvers_with_trailing_comma() -> ResolversWithTrailingComma {
ResolversWithTrailingComma {}
}
}
fn run_type_info_query<F>(type_name: &str, f: F) fn run_type_info_query<F>(type_name: &str, f: F)
where where

View file

@ -1,6 +1,8 @@
mod args; mod args;
mod field; mod field;
mod impl_object;
mod interface; mod interface;
mod object; mod object;
mod scalar; mod scalar;
mod union; mod union;
mod util;

View file

@ -18,41 +18,29 @@ Syntax to validate:
*/ */
struct Interface;
struct CustomName; struct CustomName;
graphql_object!(CustomName: () as "ACustomNamedType" |&self| {
field simple() -> i32 { 0 }
});
#[allow(dead_code)] #[allow(dead_code)]
struct WithLifetime<'a> { struct WithLifetime<'a> {
data: PhantomData<&'a i32>, data: PhantomData<&'a i32>,
} }
graphql_object!(<'a> WithLifetime<'a>: () as "WithLifetime" |&self| {
field simple() -> i32 { 0 }
});
#[allow(dead_code)] #[allow(dead_code)]
struct WithGenerics<T> { struct WithGenerics<T> {
data: T, data: T,
} }
struct DescriptionFirst;
struct FieldsFirst;
struct InterfacesFirst;
struct CommasWithTrailing;
struct CommasOnMeta;
struct Root;
graphql_object!(CustomName: () as "ACustomNamedType" |&self| {
field simple() -> i32 { 0 }
});
graphql_object!(<'a> WithLifetime<'a>: () as "WithLifetime" |&self| {
field simple() -> i32 { 0 }
});
graphql_object!(<T> WithGenerics<T>: () as "WithGenerics" |&self| { graphql_object!(<T> WithGenerics<T>: () as "WithGenerics" |&self| {
field simple() -> i32 { 0 } field simple() -> i32 { 0 }
}); });
struct Interface;
struct DescriptionFirst;
graphql_interface!(Interface: () |&self| { graphql_interface!(Interface: () |&self| {
field simple() -> i32 { 0 } field simple() -> i32 { 0 }
@ -60,7 +48,6 @@ graphql_interface!(Interface: () |&self| {
DescriptionFirst => Some(DescriptionFirst {}), DescriptionFirst => Some(DescriptionFirst {}),
} }
}); });
graphql_object!(DescriptionFirst: () |&self| { graphql_object!(DescriptionFirst: () |&self| {
description: "A description" description: "A description"
@ -69,6 +56,7 @@ graphql_object!(DescriptionFirst: () |&self| {
interfaces: [Interface] interfaces: [Interface]
}); });
struct FieldsFirst;
graphql_object!(FieldsFirst: () |&self| { graphql_object!(FieldsFirst: () |&self| {
field simple() -> i32 { 0 } field simple() -> i32 { 0 }
@ -77,6 +65,7 @@ graphql_object!(FieldsFirst: () |&self| {
interfaces: [Interface] interfaces: [Interface]
}); });
struct InterfacesFirst;
graphql_object!(InterfacesFirst: ()|&self| { graphql_object!(InterfacesFirst: ()|&self| {
interfaces: [Interface] interfaces: [Interface]
@ -85,6 +74,7 @@ graphql_object!(InterfacesFirst: ()|&self| {
description: "A description" description: "A description"
}); });
struct CommasWithTrailing;
graphql_object!(CommasWithTrailing: () |&self| { graphql_object!(CommasWithTrailing: () |&self| {
interfaces: [Interface], interfaces: [Interface],
@ -93,6 +83,8 @@ graphql_object!(CommasWithTrailing: () |&self| {
description: "A description", description: "A description",
}); });
struct CommasOnMeta;
graphql_object!(CommasOnMeta: () |&self| { graphql_object!(CommasOnMeta: () |&self| {
interfaces: [Interface], interfaces: [Interface],
description: "A description", description: "A description",
@ -100,6 +92,8 @@ graphql_object!(CommasOnMeta: () |&self| {
field simple() -> i32 { 0 } field simple() -> i32 { 0 }
}); });
struct Root;
struct InnerContext; struct InnerContext;
impl Context for InnerContext {} impl Context for InnerContext {}

View file

@ -78,12 +78,21 @@ graphql_scalar!(ScalarDescription {
} }
}); });
graphql_object!(Root: () |&self| { #[crate::impl_object_internal]
field default_name() -> DefaultName { DefaultName(0) } impl Root {
field other_order() -> OtherOrder { OtherOrder(0) } fn default_name() -> DefaultName {
field named() -> Named { Named(0) } DefaultName(0)
field scalar_description() -> ScalarDescription { ScalarDescription(0) } }
}); fn other_order() -> OtherOrder {
OtherOrder(0)
}
fn named() -> Named {
Named(0)
}
fn scalar_description() -> ScalarDescription {
ScalarDescription(0)
}
}
fn run_type_info_query<F>(doc: &str, f: F) fn run_type_info_query<F>(doc: &str, f: F)
where where

View file

@ -46,23 +46,26 @@ enum ResolversWithTrailingComma {
struct Root; struct Root;
graphql_object!(Concrete: () |&self| { #[crate::impl_object_internal]
field simple() -> i32 { 123 } impl Concrete {
}); fn simple() -> i32 {
123
}
}
graphql_union!(CustomName: () as "ACustomNamedUnion" |&self| { graphql_union!(CustomName: () as "ACustomNamedUnion" |&self| {
instance_resolvers: |&_| { instance_resolvers: |&_| {
&Concrete => match *self { CustomName::Concrete(ref c) => Some(c) } &Concrete => match *self { CustomName::Concrete(ref c) => Some(c) }
} }
}); });
graphql_union!(<'a> WithLifetime<'a>: () as "WithLifetime" |&self| { graphql_union!(<'a> WithLifetime<'a>: () as "WithLifetime" |&self| {
instance_resolvers: |&_| { instance_resolvers: |&_| {
Concrete => match *self { WithLifetime::Int(_) => Some(Concrete) } Concrete => match *self { WithLifetime::Int(_) => Some(Concrete) }
} }
}); });
graphql_union!(<T> WithGenerics<T>: () as "WithGenerics" |&self| { graphql_union!(<T> WithGenerics<T>: () as "WithGenerics" |&self| {
instance_resolvers: |&_| { instance_resolvers: |&_| {
Concrete => match *self { WithGenerics::Generic(_) => Some(Concrete) } Concrete => match *self { WithGenerics::Generic(_) => Some(Concrete) }
} }
@ -96,17 +99,30 @@ graphql_union!(ResolversWithTrailingComma: () |&self| {
description: "A description" description: "A description"
}); });
graphql_object!(<'a> Root: () as "Root" |&self| { #[crate::impl_object_internal]
field custom_name() -> CustomName { CustomName::Concrete(Concrete) } impl<'a> Root {
field with_lifetime() -> WithLifetime<'a> { WithLifetime::Int(PhantomData) } fn custom_name() -> CustomName {
field with_generics() -> WithGenerics<i32> { WithGenerics::Generic(123) } CustomName::Concrete(Concrete)
field description_first() -> DescriptionFirst { DescriptionFirst::Concrete(Concrete) } }
field resolvers_first() -> ResolversFirst { ResolversFirst::Concrete(Concrete) } fn with_lifetime() -> WithLifetime<'a> {
field commas_with_trailing() -> CommasWithTrailing { CommasWithTrailing::Concrete(Concrete) } WithLifetime::Int(PhantomData)
field resolvers_with_trailing_comma() -> ResolversWithTrailingComma { }
fn with_generics() -> WithGenerics<i32> {
WithGenerics::Generic(123)
}
fn description_first() -> DescriptionFirst {
DescriptionFirst::Concrete(Concrete)
}
fn resolvers_first() -> ResolversFirst {
ResolversFirst::Concrete(Concrete)
}
fn commas_with_trailing() -> CommasWithTrailing {
CommasWithTrailing::Concrete(Concrete)
}
fn resolvers_with_trailing_comma() -> ResolversWithTrailingComma {
ResolversWithTrailingComma::Concrete(Concrete) ResolversWithTrailingComma::Concrete(Concrete)
} }
}); }
fn run_type_info_query<F>(type_name: &str, f: F) fn run_type_info_query<F>(type_name: &str, f: F)
where where

View file

@ -0,0 +1,54 @@
use crate::{DefaultScalarValue, GraphQLType, RootNode, Value, Variables};
use std::default::Default;
pub fn run_query<Query, Mutation, Context>(query: &str) -> Value
where
Query: GraphQLType<DefaultScalarValue, TypeInfo = (), Context = Context> + Default,
Mutation: GraphQLType<DefaultScalarValue, TypeInfo = (), Context = Context> + Default,
Context: Default,
{
let schema = RootNode::new(Query::default(), Mutation::default());
let (result, errs) =
crate::execute(query, None, &schema, &Variables::new(), &Context::default())
.expect("Execution failed");
assert_eq!(errs, []);
result
}
pub fn run_info_query<Query, Mutation, Context>(type_name: &str) -> Value
where
Query: GraphQLType<DefaultScalarValue, TypeInfo = (), Context = Context> + Default,
Mutation: GraphQLType<DefaultScalarValue, TypeInfo = (), Context = Context> + Default,
Context: Default,
{
let query = format!(
r#"
{{
__type(name: "{}") {{
name,
description,
fields {{
name
description
args {{
name
description
type {{
name
}}
}}
}}
}}
}}
"#,
type_name
);
let result = run_query::<Query, Mutation, Context>(&query);
result
.as_object_value()
.expect("Result is not an object")
.get_field_value("__type")
.expect("__type field missing")
.clone()
}

View file

@ -31,27 +31,31 @@ struct Foo {
struct Query; struct Query;
graphql_object!(Query: () where Scalar = <S> |&self| { #[crate::impl_object_internal(Scalar = S)]
field int_field() -> i32 { impl<'a, S> Query
where
S: crate::ScalarValue + 'a,
{
fn int_field() -> i32 {
42 42
} }
field float_field() -> f64 { fn float_field() -> f64 {
3.14 3.14
} }
field string_field() -> String { fn string_field() -> String {
"".into() "".into()
} }
field bool_field() -> bool { fn bool_field() -> bool {
true true
} }
field enum_field(_foo: Foo) -> Enum { fn enum_field(_foo: Foo) -> Enum {
Enum::EnumValue Enum::EnumValue
} }
}); }
fn scalar_meta<T>(name: &'static str) -> MetaType<DefaultScalarValue> fn scalar_meta<T>(name: &'static str) -> MetaType<DefaultScalarValue>
where where

View file

@ -73,52 +73,68 @@ where
} }
} }
graphql_object!(<'a> SchemaType<'a, S>: SchemaType<'a, S> as "__Schema" #[crate::impl_object_internal(
where Scalar = <S: 'a> |&self| name = "__Schema"
Context = SchemaType<'a, S>,
Scalar = S,
)]
impl<'a, S> SchemaType<'a, S>
where
S: crate::ScalarValue + 'a,
{ {
field types() -> Vec<TypeType<S>> { fn types(&self) -> Vec<TypeType<S>> {
self.type_list() self.type_list()
.into_iter() .into_iter()
.filter(|t| t.to_concrete().map(|t| t.name() != Some("_EmptyMutation")).unwrap_or(false)) .filter(|t| {
.collect() t.to_concrete()
.map(|t| t.name() != Some("_EmptyMutation"))
.unwrap_or(false)
})
.collect::<Vec<_>>()
} }
field query_type() -> TypeType<S> { fn query_type(&self) -> TypeType<S> {
self.query_type() self.query_type()
} }
field mutation_type() -> Option<TypeType<S>> { fn mutation_type(&self) -> Option<TypeType<S>> {
self.mutation_type() self.mutation_type()
} }
// Included for compatibility with the introspection query in GraphQL.js // Included for compatibility with the introspection query in GraphQL.js
field subscription_type() -> Option<TypeType<S>> { fn subscription_type(&self) -> Option<TypeType<S>> {
None None
} }
field directives() -> Vec<&DirectiveType<S>> { fn directives(&self) -> Vec<&DirectiveType<S>> {
self.directive_list() self.directive_list()
} }
}); }
graphql_object!(<'a> TypeType<'a, S>: SchemaType<'a, S> as "__Type" #[crate::impl_object_internal(
where Scalar = <S: 'a> |&self| name = "__Type"
Context = SchemaType<'a, S>,
Scalar = S,
)]
impl<'a, S> TypeType<'a, S>
where
S: crate::ScalarValue + 'a,
{ {
field name() -> Option<&str> { fn name(&self) -> Option<&str> {
match *self { match *self {
TypeType::Concrete(t) => t.name(), TypeType::Concrete(t) => t.name(),
_ => None, _ => None,
} }
} }
field description() -> Option<&String> { fn description(&self) -> Option<&String> {
match *self { match *self {
TypeType::Concrete(t) => t.description(), TypeType::Concrete(t) => t.description(),
_ => None, _ => None,
} }
} }
field kind() -> TypeKind { fn kind(&self) -> TypeKind {
match *self { match *self {
TypeType::Concrete(t) => t.type_kind(), TypeType::Concrete(t) => t.type_kind(),
TypeType::List(_) => TypeKind::List, TypeType::List(_) => TypeKind::List,
@ -126,190 +142,242 @@ graphql_object!(<'a> TypeType<'a, S>: SchemaType<'a, S> as "__Type"
} }
} }
field fields(include_deprecated = false: bool) -> Option<Vec<&Field<S>>> { #[graphql(arguments(include_deprecated(default = false)))]
fn fields(&self, include_deprecated: bool) -> Option<Vec<&Field<S>>> {
match *self { match *self {
TypeType::Concrete(&MetaType::Interface(InterfaceMeta { ref fields, .. })) | TypeType::Concrete(&MetaType::Interface(InterfaceMeta { ref fields, .. }))
TypeType::Concrete(&MetaType::Object(ObjectMeta { ref fields, .. })) => | TypeType::Concrete(&MetaType::Object(ObjectMeta { ref fields, .. })) => {
Some(fields Some(
.iter() fields
.filter(|f| include_deprecated || !f.deprecation_status.is_deprecated()) .iter()
.filter(|f| !f.name.starts_with("__")) .filter(|f| include_deprecated || !f.deprecation_status.is_deprecated())
.collect()), .filter(|f| !f.name.starts_with("__"))
.collect(),
)
}
_ => None, _ => None,
} }
} }
field of_type() -> Option<&Box<TypeType<S>>> { fn of_type(&self) -> Option<&Box<TypeType<S>>> {
match *self { match *self {
TypeType::Concrete(_) => None, TypeType::Concrete(_) => None,
TypeType::List(ref l) | TypeType::NonNull(ref l) => Some(l), TypeType::List(ref l) | TypeType::NonNull(ref l) => Some(l),
} }
} }
field input_fields() -> Option<&Vec<Argument<S>>> { fn input_fields(&self) -> Option<&Vec<Argument<S>>> {
match *self { match *self {
TypeType::Concrete(&MetaType::InputObject(InputObjectMeta { ref input_fields, .. })) => TypeType::Concrete(&MetaType::InputObject(InputObjectMeta {
Some(input_fields), ref input_fields,
..
})) => Some(input_fields),
_ => None, _ => None,
} }
} }
field interfaces(&executor) -> Option<Vec<TypeType<S>>> { fn interfaces(&self, schema: &SchemaType<'a, S>) -> Option<Vec<TypeType<S>>> {
match *self { match *self {
TypeType::Concrete(&MetaType::Object(ObjectMeta { ref interface_names, .. })) => { TypeType::Concrete(&MetaType::Object(ObjectMeta {
let schema = executor.context(); ref interface_names,
Some(interface_names ..
})) => Some(
interface_names
.iter() .iter()
.filter_map(|n| schema.type_by_name(n)) .filter_map(|n| schema.type_by_name(n))
.collect()) .collect(),
} ),
_ => None, _ => None,
} }
} }
field possible_types(&executor) -> Option<Vec<TypeType<S>>> { fn possible_types(&self, schema: &SchemaType<'a, S>) -> Option<Vec<TypeType<S>>> {
let schema = executor.context();
match *self { match *self {
TypeType::Concrete(&MetaType::Union(UnionMeta { ref of_type_names, .. })) => { TypeType::Concrete(&MetaType::Union(UnionMeta {
Some(of_type_names ref of_type_names, ..
})) => Some(
of_type_names
.iter() .iter()
.filter_map(|tn| schema.type_by_name(tn)) .filter_map(|tn| schema.type_by_name(tn))
.collect()) .collect(),
} ),
TypeType::Concrete(&MetaType::Interface(InterfaceMeta{name: ref iface_name, .. })) => { TypeType::Concrete(&MetaType::Interface(InterfaceMeta {
Some(schema.concrete_type_list() name: ref iface_name,
..
})) => Some(
schema
.concrete_type_list()
.iter() .iter()
.filter_map(|&ct| .filter_map(|&ct| {
if let MetaType::Object(ObjectMeta{ if let MetaType::Object(ObjectMeta {
ref name, ref name,
ref interface_names, ref interface_names,
.. ..
}) = *ct { }) = *ct
{
if interface_names.contains(&iface_name.to_string()) { if interface_names.contains(&iface_name.to_string()) {
schema.type_by_name(name) schema.type_by_name(name)
} else { None } } else {
} else { None } None
) }
.collect()) } else {
None
}
})
.collect(),
),
_ => None,
}
}
#[graphql(arguments(include_deprecated(default = false)))]
fn enum_values(&self, include_deprecated: bool) -> Option<Vec<&EnumValue>> {
match *self {
TypeType::Concrete(&MetaType::Enum(EnumMeta { ref values, .. })) => {
Some(
values
.iter()
.filter(|f| include_deprecated || !f.deprecation_status.is_deprecated())
.collect(),
)
} }
_ => None, _ => None,
} }
} }
}
field enum_values(include_deprecated = false: bool) -> Option<Vec<&EnumValue>> { #[crate::impl_object_internal(
match *self { name = "__Field",
TypeType::Concrete(&MetaType::Enum(EnumMeta { ref values, .. })) => Context = SchemaType<'a, S>,
Some(values Scalar = S,
.iter() )]
.filter(|f| include_deprecated || !f.deprecation_status.is_deprecated()) impl<'a, S> Field<'a, S>
.collect()), where
_ => None, S: crate::ScalarValue + 'a,
}
}
});
graphql_object!(<'a> Field<'a, S>: SchemaType<'a, S> as "__Field"
where Scalar = <S: 'a> |&self|
{ {
field name() -> &String { fn name(&self) -> &String {
&self.name &self.name
} }
field description() -> &Option<String> { fn description(&self) -> &Option<String> {
&self.description &self.description
} }
field args() -> Vec<&Argument<S>> { fn args(&self) -> Vec<&Argument<S>> {
self.arguments.as_ref().map_or_else(Vec::new, |v| v.iter().collect()) self.arguments
.as_ref()
.map_or_else(Vec::new, |v| v.iter().collect())
} }
field type(&executor) -> TypeType<S> { #[graphql(name = "type")]
executor.context().make_type(&self.field_type) fn _type(&self, context: &SchemaType<'a, S>) -> TypeType<S> {
context.make_type(&self.field_type)
} }
field is_deprecated() -> bool { fn is_deprecated(&self) -> bool {
self.deprecation_status.is_deprecated() self.deprecation_status.is_deprecated()
} }
field deprecation_reason() -> Option<&String> { fn deprecation_reason(&self) -> Option<&String> {
self.deprecation_status.reason() self.deprecation_status.reason()
} }
}); }
graphql_object!(<'a> Argument<'a, S>: SchemaType<'a, S> as "__InputValue" #[crate::impl_object_internal(
where Scalar = <S: 'a> |&self| name = "__InputValue",
Context = SchemaType<'a, S>,
Scalar = S,
)]
impl<'a, S> Argument<'a, S>
where
S: crate::ScalarValue + 'a,
{ {
field name() -> &String { fn name(&self) -> &String {
&self.name &self.name
} }
field description() -> &Option<String> { fn description(&self) -> &Option<String> {
&self.description &self.description
} }
field type(&executor) -> TypeType<S> { #[graphql(name = "type")]
executor.context().make_type(&self.arg_type) fn _type(&self, context: &SchemaType<'a, S>) -> TypeType<S> {
context.make_type(&self.arg_type)
} }
field default_value() -> Option<String> { fn default_value(&self) -> Option<String> {
self.default_value.as_ref().map(|v| format!("{}", v)) self.default_value.as_ref().map(|v| format!("{}", v))
} }
}); }
graphql_object!(EnumValue: () as "__EnumValue" where Scalar = <S> |&self| { #[crate::impl_object_internal(
field name() -> &String { name = "__EnumValue",
Scalar = S,
)]
impl<'a, S> EnumValue
where
S: crate::ScalarValue + 'a,
{
fn name(&self) -> &String {
&self.name &self.name
} }
field description() -> &Option<String> { fn description(&self) -> &Option<String> {
&self.description &self.description
} }
field is_deprecated() -> bool { fn is_deprecated(&self) -> bool {
self.deprecation_status.is_deprecated() self.deprecation_status.is_deprecated()
} }
field deprecation_reason() -> Option<&String> { fn deprecation_reason(&self) -> Option<&String> {
self.deprecation_status.reason() self.deprecation_status.reason()
} }
}); }
graphql_object!(<'a> DirectiveType<'a, S>: SchemaType<'a, S> as "__Directive" #[crate::impl_object_internal(
where Scalar = <S: 'a> |&self| name = "__Directive",
Context = SchemaType<'a, S>,
Scalar = S,
)]
impl<'a, S> DirectiveType<'a, S>
where
S: crate::ScalarValue + 'a,
{ {
field name() -> &String { fn name(&self) -> &String {
&self.name &self.name
} }
field description() -> &Option<String> { fn description(&self) -> &Option<String> {
&self.description &self.description
} }
field locations() -> &Vec<DirectiveLocation> { fn locations(&self) -> &Vec<DirectiveLocation> {
&self.locations &self.locations
} }
field args() -> &Vec<Argument<S>> { fn args(&self) -> &Vec<Argument<S>> {
&self.arguments &self.arguments
} }
// Included for compatibility with the introspection query in GraphQL.js // Included for compatibility with the introspection query in GraphQL.js
field deprecated "Use the locations array instead" #[graphql(deprecated = "Use the locations array instead")]
on_operation() -> bool { fn on_operation(&self) -> bool {
self.locations.contains(&DirectiveLocation::Query) self.locations.contains(&DirectiveLocation::Query)
} }
// Included for compatibility with the introspection query in GraphQL.js // Included for compatibility with the introspection query in GraphQL.js
field deprecated "Use the locations array instead" #[graphql(deprecated = "Use the locations array instead")]
on_fragment() -> bool { fn on_fragment(&self) -> bool {
self.locations.contains(&DirectiveLocation::FragmentDefinition) || self.locations
self.locations.contains(&DirectiveLocation::InlineFragment) || .contains(&DirectiveLocation::FragmentDefinition)
self.locations.contains(&DirectiveLocation::FragmentSpread) || self.locations.contains(&DirectiveLocation::InlineFragment)
|| self.locations.contains(&DirectiveLocation::FragmentSpread)
} }
// Included for compatibility with the introspection query in GraphQL.js // Included for compatibility with the introspection query in GraphQL.js
field deprecated "Use the locations array instead" #[graphql(deprecated = "Use the locations array instead")]
on_field() -> bool { fn on_field(&self) -> bool {
self.locations.contains(&DirectiveLocation::Field) self.locations.contains(&DirectiveLocation::Field)
} }
}); }

View file

@ -29,80 +29,93 @@ graphql_interface!(<'a> &'a Character: Database as "Character" |&self| {
} }
}); });
graphql_object!(<'a> &'a Human: Database as "Human" |&self| { #[crate::impl_object_internal(
description: "A humanoid creature in the Star Wars universe." Context = Database,
Scalar = crate::DefaultScalarValue,
interfaces: [&Character] interfaces = [&dyn Character],
)]
field id() -> &str as "The id of the human"{ /// A humanoid creature in the Star Wars universe.
impl<'a> &'a Human {
/// The id of the human
fn id(&self) -> &str {
self.id() self.id()
} }
field name() -> Option<&str> as "The name of the human" { /// The name of the human
fn name(&self) -> Option<&str> {
Some(self.name()) Some(self.name())
} }
field friends(&executor) -> Vec<&Character> /// The friends of the human
as "The friends of the human" { fn friends(&self, ctx: &Database) -> Vec<&Character> {
executor.context().get_friends(self.as_character()) ctx.get_friends(self.as_character())
} }
field appears_in() -> &[Episode] as "Which movies they appear in" { /// Which movies they appear in
fn appears_in(&self) -> &[Episode] {
self.appears_in() self.appears_in()
} }
field home_planet() -> &Option<String> as "The home planet of the human" { /// The home planet of the human
fn home_planet(&self) -> &Option<String> {
self.home_planet() self.home_planet()
} }
}); }
graphql_object!(<'a> &'a Droid: Database as "Droid" |&self| { #[crate::impl_object_internal(
description: "A mechanical creature in the Star Wars universe." Context = Database,
Scalar = crate::DefaultScalarValue,
interfaces: [&Character] interfaces = [&dyn Character],
)]
field id() -> &str as "The id of the droid" { /// A mechanical creature in the Star Wars universe.
impl<'a> &'a Droid {
/// The id of the droid
fn id(&self) -> &str {
self.id() self.id()
} }
field name() -> Option<&str> as "The name of the droid" { /// The name of the droid
fn name(&self) -> Option<&str> {
Some(self.name()) Some(self.name())
} }
field friends(&executor) -> Vec<&Character> /// The friends of the droid
as "The friends of the droid" { fn friends(&self, ctx: &Database) -> Vec<&Character> {
executor.context().get_friends(self.as_character()) ctx.get_friends(self.as_character())
} }
field appears_in() -> &[Episode] as "Which movies they appear in" { /// Which movies they appear in
fn appears_in(&self) -> &[Episode] {
self.appears_in() self.appears_in()
} }
field primary_function() -> &Option<String> as "The primary function of the droid" { /// The primary function of the droid
fn primary_function(&self) -> &Option<String> {
self.primary_function() self.primary_function()
} }
}); }
graphql_object!(Database: Database as "Query" |&self| { #[crate::impl_object_internal(
description: "The root query object of the schema" name = "Query",
Context = Database,
field human( Scalar = crate::DefaultScalarValue,
id: String as "id of the human" )]
) -> Option<&Human> { /// The root query object of the schema
impl Database {
#[graphql(arguments(id(description = "id of the human")))]
fn human(&self, id: String) -> Option<&Human> {
self.get_human(&id) self.get_human(&id)
} }
field droid( #[graphql(arguments(id(description = "id of the droid")))]
id: String as "id of the droid" fn droid(&self, id: String) -> Option<&Droid> {
) -> Option<&Droid> {
self.get_droid(&id) self.get_droid(&id)
} }
field hero( #[graphql(arguments(episode(
episode: Option<Episode> as description = "If omitted, returns the hero of the whole saga. If provided, returns the hero of that particular episode"
"If omitted, returns the hero of the whole saga. If provided, returns \ )))]
the hero of that particular episode" fn hero(&self, episode: Option<Episode>) -> Option<&Character> {
) -> Option<&Character> {
Some(self.get_hero(episode).as_character()) Some(self.get_hero(episode).as_character())
} }
}); }

View file

@ -344,56 +344,56 @@ pub(crate) fn schema_introspection_result() -> value::Value {
{ {
"kind": "ENUM", "kind": "ENUM",
"name": "__TypeKind", "name": "__TypeKind",
"description": "GraphQL type kind\nThe GraphQL specification defines a number of type kinds - the meta type of a type.", "description": "GraphQL type kind\n\nThe GraphQL specification defines a number of type kinds - the meta type of a type.",
"fields": Null, "fields": Null,
"inputFields": Null, "inputFields": Null,
"interfaces": Null, "interfaces": Null,
"enumValues": [ "enumValues": [
{ {
"name": "SCALAR", "name": "SCALAR",
"description": "## Scalar types\nScalar types appear as the leaf nodes of GraphQL queries. Strings, numbers, and booleans are the built in types, and while it's possible to define your own, it's relatively uncommon.", "description": "## Scalar types\n\nScalar types appear as the leaf nodes of GraphQL queries. Strings, numbers, and booleans are the built in types, and while it's possible to define your own, it's relatively uncommon.",
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": Null "deprecationReason": Null
}, },
{ {
"name": "OBJECT", "name": "OBJECT",
"description": "## Object types\nThe most common type to be implemented by users. Objects have fields and can implement interfaces.", "description": "## Object types\n\nThe most common type to be implemented by users. Objects have fields and can implement interfaces.",
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": Null "deprecationReason": Null
}, },
{ {
"name": "INTERFACE", "name": "INTERFACE",
"description": "## Interface types\nInterface types are used to represent overlapping fields between multiple types, and can be queried for their concrete type.", "description": "## Interface types\n\nInterface types are used to represent overlapping fields between multiple types, and can be queried for their concrete type.",
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": Null "deprecationReason": Null
}, },
{ {
"name": "UNION", "name": "UNION",
"description": "## Union types\nUnions are similar to interfaces but can not contain any fields on their own.", "description": "## Union types\n\nUnions are similar to interfaces but can not contain any fields on their own.",
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": Null "deprecationReason": Null
}, },
{ {
"name": "ENUM", "name": "ENUM",
"description": "## Enum types\nLike scalars, enum types appear as the leaf nodes of GraphQL queries.", "description": "## Enum types\n\nLike scalars, enum types appear as the leaf nodes of GraphQL queries.",
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": Null "deprecationReason": Null
}, },
{ {
"name": "INPUT_OBJECT", "name": "INPUT_OBJECT",
"description": "## Input objects\nRepresents complex values provided in queries _into_ the system.", "description": "## Input objects\n\nRepresents complex values provided in queries _into_ the system.",
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": Null "deprecationReason": Null
}, },
{ {
"name": "LIST", "name": "LIST",
"description": "## List types\nRepresent lists of other types. This library provides implementations for vectors and slices, but other Rust types can be extended to serve as GraphQL lists.", "description": "## List types\n\nRepresent lists of other types. This library provides implementations for vectors and slices, but other Rust types can be extended to serve as GraphQL lists.",
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": Null "deprecationReason": Null
}, },
{ {
"name": "NON_NULL", "name": "NON_NULL",
"description": "## Non-null types\nIn GraphQL, nullable types are the default. By putting a `!` after a type, it becomes non-nullable.", "description": "## Non-null types\n\nIn GraphQL, nullable types are the default. By putting a `!` after a type, it becomes non-nullable.",
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": Null "deprecationReason": Null
} }
@ -827,6 +827,7 @@ pub(crate) fn schema_introspection_result() -> value::Value {
"args": [ "args": [
{ {
"name": "id", "name": "id",
"description": Null,
"description": "id of the droid", "description": "id of the droid",
"type": { "type": {
"kind": "NON_NULL", "kind": "NON_NULL",

View file

@ -12,33 +12,33 @@ use crate::schema::meta::{Argument, MetaType};
/// GraphQL type kind /// GraphQL type kind
/// ///
/// The GraphQL specification defines a number of type kinds - the meta type /// The GraphQL specification defines a number of type kinds - the meta type\
/// of a type. /// of a type.
#[derive(Clone, Eq, PartialEq, Debug, GraphQLEnum)] #[derive(Clone, Eq, PartialEq, Debug, GraphQLEnum)]
#[graphql(name = "__TypeKind")] #[graphql(name = "__TypeKind")]
pub enum TypeKind { pub enum TypeKind {
/// ## Scalar types /// ## Scalar types
/// ///
/// Scalar types appear as the leaf nodes of GraphQL queries. Strings, /// Scalar types appear as the leaf nodes of GraphQL queries. Strings,\
/// numbers, and booleans are the built in types, and while it's possible /// numbers, and booleans are the built in types, and while it's possible\
/// to define your own, it's relatively uncommon. /// to define your own, it's relatively uncommon.
Scalar, Scalar,
/// ## Object types /// ## Object types
/// ///
/// The most common type to be implemented by users. Objects have fields /// The most common type to be implemented by users. Objects have fields\
/// and can implement interfaces. /// and can implement interfaces.
Object, Object,
/// ## Interface types /// ## Interface types
/// ///
/// Interface types are used to represent overlapping fields between /// Interface types are used to represent overlapping fields between\
/// multiple types, and can be queried for their concrete type. /// multiple types, and can be queried for their concrete type.
Interface, Interface,
/// ## Union types /// ## Union types
/// ///
/// Unions are similar to interfaces but can not contain any fields on /// Unions are similar to interfaces but can not contain any fields on\
/// their own. /// their own.
Union, Union,
@ -55,14 +55,14 @@ pub enum TypeKind {
/// ## List types /// ## List types
/// ///
/// Represent lists of other types. This library provides implementations /// Represent lists of other types. This library provides implementations\
/// for vectors and slices, but other Rust types can be extended to serve /// for vectors and slices, but other Rust types can be extended to serve\
/// as GraphQL lists. /// as GraphQL lists.
List, List,
/// ## Non-null types /// ## Non-null types
/// ///
/// In GraphQL, nullable types are the default. By putting a `!` after a /// In GraphQL, nullable types are the default. By putting a `!` after a\
/// type, it becomes non-nullable. /// type, it becomes non-nullable.
#[graphql(name = "NON_NULL")] #[graphql(name = "NON_NULL")]
NonNull, NonNull,