Fix input value coercion with defaults (#1080, #1073)

This commit is contained in:
ilslv 2022-07-09 00:55:51 +03:00 committed by GitHub
parent 5332db0a4b
commit 0c8bcf582f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 366 additions and 72 deletions

View file

@ -48,7 +48,7 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
- Reworked [`chrono` crate] integration GraphQL scalars according to [graphql-scalars.dev] specs: ([#1010]) - Reworked [`chrono` crate] integration GraphQL scalars according to [graphql-scalars.dev] specs: ([#1010])
- Disabled `chrono` [Cargo feature] by default. - Disabled `chrono` [Cargo feature] by default.
- Removed `scalar-naivetime` [Cargo feature]. - Removed `scalar-naivetime` [Cargo feature].
- Removed lifetime parameter from `ParseError`, `GraphlQLError`, `GraphQLBatchRequest` and `GraphQLRequest`. ([#528]) - Removed lifetime parameter from `ParseError`, `GraphlQLError`, `GraphQLBatchRequest` and `GraphQLRequest`. ([#1081], [#528])
### Added ### Added
@ -72,8 +72,10 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
- Unsupported expressions in `graphql_value!` macro. ([#996], [#503]) - Unsupported expressions in `graphql_value!` macro. ([#996], [#503])
- Incorrect GraphQL list coercion rules: `null` cannot be coerced to an `[Int!]!` or `[Int]!`. ([#1004]) - Incorrect GraphQL list coercion rules: `null` cannot be coerced to an `[Int!]!` or `[Int]!`. ([#1004])
- All procedural macros expansion inside `macro_rules!`. ([#1054], [#1051]) - All procedural macros expansion inside `macro_rules!`. ([#1054], [#1051])
- Incorrect input value coercion with defaults. ([#1080], [#1073])
[#503]: /../../issues/503 [#503]: /../../issues/503
[#528]: /../../issues/528
[#750]: /../../issues/750 [#750]: /../../issues/750
[#798]: /../../issues/798 [#798]: /../../issues/798
[#918]: /../../issues/918 [#918]: /../../issues/918
@ -101,6 +103,9 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
[#1054]: /../../pull/1054 [#1054]: /../../pull/1054
[#1057]: /../../pull/1057 [#1057]: /../../pull/1057
[#1060]: /../../pull/1060 [#1060]: /../../pull/1060
[#1073]: /../../issues/1073
[#1080]: /../../pull/1080
[#1081]: /../../pull/1081
[ba1ed85b]: /../../commit/ba1ed85b3c3dd77fbae7baf6bc4e693321a94083 [ba1ed85b]: /../../commit/ba1ed85b3c3dd77fbae7baf6bc4e693321a94083

View file

@ -295,25 +295,36 @@ impl<S> InputValue<S> {
Self::Object(o) Self::Object(o)
} }
/// Resolve all variables to their values. /// Resolves all variables of this [`InputValue`] to their actual `values`.
///
/// If a variable is not present in the `values`:
/// - Returns [`None`] in case this is an [`InputValue::Variable`].
/// - Skips field in case of an [`InputValue::Object`] field.
/// - Replaces with an [`InputValue::Null`] in case of an
/// [`InputValue::List`] element.
///
/// This is done, because for an [`InputValue::Variable`] (or an
/// [`InputValue::Object`] field) a default value can be used later, if it's
/// provided. While on contrary, a single [`InputValue::List`] element
/// cannot have a default value.
#[must_use] #[must_use]
pub fn into_const(self, vars: &Variables<S>) -> Self pub fn into_const(self, values: &Variables<S>) -> Option<Self>
where where
S: Clone, S: Clone,
{ {
match self { match self {
Self::Variable(v) => vars.get(&v).map_or_else(InputValue::null, Clone::clone), Self::Variable(v) => values.get(&v).cloned(),
Self::List(l) => Self::List( Self::List(l) => Some(Self::List(
l.into_iter() l.into_iter()
.map(|s| s.map(|v| v.into_const(vars))) .map(|s| s.map(|v| v.into_const(values).unwrap_or_else(Self::null)))
.collect(), .collect(),
), )),
Self::Object(o) => Self::Object( Self::Object(o) => Some(Self::Object(
o.into_iter() o.into_iter()
.map(|(sk, sv)| (sk, sv.map(|v| v.into_const(vars)))) .filter_map(|(sk, sv)| sv.and_then(|v| v.into_const(values)).map(|sv| (sk, sv)))
.collect(), .collect(),
), )),
v => v, v => Some(v),
} }
} }

View file

@ -1227,9 +1227,6 @@ impl<'r, S: 'r> Registry<'r, S> {
} }
/// Creates an [`Argument`] with the provided default `value`. /// Creates an [`Argument`] with the provided default `value`.
///
/// When called with type `T`, the actual [`Argument`] will be given the
/// type `Option<T>`.
pub fn arg_with_default<T>( pub fn arg_with_default<T>(
&mut self, &mut self,
name: &str, name: &str,
@ -1240,7 +1237,7 @@ impl<'r, S: 'r> Registry<'r, S> {
T: GraphQLType<S> + ToInputValue<S> + FromInputValue<S>, T: GraphQLType<S> + ToInputValue<S> + FromInputValue<S>,
S: ScalarValue, S: ScalarValue,
{ {
Argument::new(name, self.get_type::<Option<T>>(info)).default_value(value.to_input_value()) Argument::new(name, self.get_type::<T>(info)).default_value(value.to_input_value())
} }
fn insert_placeholder(&mut self, name: Name, of_type: Type<'r>) { fn insert_placeholder(&mut self, name: Name, of_type: Type<'r>) {

View file

@ -462,6 +462,9 @@ async fn field_with_defaults_introspection() {
name name
type { type {
name name
ofType {
name
}
} }
defaultValue defaultValue
} }
@ -477,12 +480,12 @@ async fn field_with_defaults_introspection() {
assert_eq!(fields.len(), 2); assert_eq!(fields.len(), 2);
assert!(fields.contains(&graphql_value!({ assert!(fields.contains(&graphql_value!({
"name": "fieldOne", "name": "fieldOne",
"type": {"name": "Int"}, "type": {"name": null, "ofType": {"name": "Int"}},
"defaultValue": "123", "defaultValue": "123",
}))); })));
assert!(fields.contains(&graphql_value!({ assert!(fields.contains(&graphql_value!({
"name": "fieldTwo", "name": "fieldTwo",
"type": {"name": "Int"}, "type": {"name": null, "ofType": {"name": "Int"}},
"defaultValue": "456", "defaultValue": "456",
}))); })));
}) })

View file

@ -444,10 +444,14 @@ async fn object_introspection() {
"name": "second", "name": "second",
"description": "The second number", "description": "The second number",
"type": { "type": {
"name": null,
"kind": "NON_NULL",
"ofType": {
"name": "Int", "name": "Int",
"kind": "SCALAR", "kind": "SCALAR",
"ofType": null, "ofType": null,
}, },
},
"defaultValue": "123", "defaultValue": "123",
}], }],
"type": { "type": {

View file

@ -75,6 +75,12 @@ impl TestType {
format!("{:?}", input) format!("{:?}", input)
} }
fn nullable_field_with_default_argument_value(
#[graphql(default = "Hello World".to_owned())] input: Option<String>,
) -> String {
format!("{:?}", input)
}
fn field_with_nested_object_input(input: Option<TestNestedInputObject>) -> String { fn field_with_nested_object_input(input: Option<TestNestedInputObject>) -> String {
format!("{:?}", input) format!("{:?}", input)
} }
@ -791,13 +797,14 @@ async fn default_argument_when_not_provided() {
} }
#[tokio::test] #[tokio::test]
async fn default_argument_when_nullable_variable_not_provided() { async fn provided_variable_overwrites_default_value() {
run_query( run_variable_query(
r#"query q($input: String) { fieldWithDefaultArgumentValue(input: $input) }"#, r#"query q($input: String!) { fieldWithDefaultArgumentValue(input: $input) }"#,
graphql_vars! {"input": "Overwritten"},
|result| { |result| {
assert_eq!( assert_eq!(
result.get_field_value("fieldWithDefaultArgumentValue"), result.get_field_value("fieldWithDefaultArgumentValue"),
Some(&graphql_value!(r#""Hello World""#)), Some(&graphql_value!(r#""Overwritten""#)),
); );
}, },
) )
@ -805,14 +812,28 @@ async fn default_argument_when_nullable_variable_not_provided() {
} }
#[tokio::test] #[tokio::test]
async fn default_argument_when_nullable_variable_set_to_null() { async fn default_argument_when_nullable_variable_not_provided() {
run_query(
r#"query q($input: String) { nullableFieldWithDefaultArgumentValue(input: $input) }"#,
|result| {
assert_eq!(
result.get_field_value("nullableFieldWithDefaultArgumentValue"),
Some(&graphql_value!(r#"Some("Hello World")"#)),
);
},
)
.await;
}
#[tokio::test]
async fn null_when_nullable_variable_of_argument_with_default_value_set_to_null() {
run_variable_query( run_variable_query(
r#"query q($input: String) { fieldWithDefaultArgumentValue(input: $input) }"#, r#"query q($input: String) { nullableFieldWithDefaultArgumentValue(input: $input) }"#,
graphql_vars! {"input": null}, graphql_vars! {"input": null},
|result| { |result| {
assert_eq!( assert_eq!(
result.get_field_value("fieldWithDefaultArgumentValue"), result.get_field_value("nullableFieldWithDefaultArgumentValue"),
Some(&graphql_value!(r#""Hello World""#)), Some(&graphql_value!(r#"None"#)),
); );
}, },
) )

View file

@ -81,7 +81,7 @@ impl<T> Spanning<T> {
} }
} }
/// Modify the contents of the spanned item /// Modify the contents of the spanned item.
pub fn map<O, F: Fn(T) -> O>(self, f: F) -> Spanning<O> { pub fn map<O, F: Fn(T) -> O>(self, f: F) -> Spanning<O> {
Spanning { Spanning {
item: f(self.item), item: f(self.item),
@ -89,6 +89,13 @@ impl<T> Spanning<T> {
end: self.end, end: self.end,
} }
} }
/// Modifies the contents of the spanned item in case `f` returns [`Some`],
/// or returns [`None`] otherwise.
pub fn and_then<O, F: Fn(T) -> Option<O>>(self, f: F) -> Option<Spanning<O>> {
let (start, end) = (self.start, self.end);
f(self.item).map(|item| Spanning { item, start, end })
}
} }
impl<T: fmt::Display> fmt::Display for Spanning<T> { impl<T: fmt::Display> fmt::Display for Spanning<T> {

View file

@ -211,13 +211,19 @@ impl<'a, S: ScalarValue + 'a> TypeType<'a, S> {
} }
} }
fn fields(&self, #[graphql(default)] include_deprecated: bool) -> Option<Vec<&Field<S>>> { fn fields(
&self,
#[graphql(default = false)] include_deprecated: Option<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, .. })) => Some( | TypeType::Concrete(&MetaType::Object(ObjectMeta { ref fields, .. })) => Some(
fields fields
.iter() .iter()
.filter(|f| include_deprecated || !f.deprecation_status.is_deprecated()) .filter(|f| {
include_deprecated.unwrap_or_default()
|| !f.deprecation_status.is_deprecated()
})
.filter(|f| !f.name.starts_with("__")) .filter(|f| !f.name.starts_with("__"))
.collect(), .collect(),
), ),
@ -302,12 +308,18 @@ impl<'a, S: ScalarValue + 'a> TypeType<'a, S> {
} }
} }
fn enum_values(&self, #[graphql(default)] include_deprecated: bool) -> Option<Vec<&EnumValue>> { fn enum_values(
&self,
#[graphql(default = false)] include_deprecated: Option<bool>,
) -> Option<Vec<&EnumValue>> {
match self { match self {
TypeType::Concrete(&MetaType::Enum(EnumMeta { ref values, .. })) => Some( TypeType::Concrete(&MetaType::Enum(EnumMeta { ref values, .. })) => Some(
values values
.iter() .iter()
.filter(|f| include_deprecated || !f.deprecation_status.is_deprecated()) .filter(|f| {
include_deprecated.unwrap_or_default()
|| !f.deprecation_status.is_deprecated()
})
.collect(), .collect(),
), ),
_ => None, _ => None,

View file

@ -242,7 +242,9 @@ where
f.arguments.as_ref().map(|m| { f.arguments.as_ref().map(|m| {
m.item m.item
.iter() .iter()
.map(|&(ref k, ref v)| (k.item, v.item.clone().into_const(exec_vars))) .filter_map(|&(ref k, ref v)| {
v.item.clone().into_const(exec_vars).map(|v| (k.item, v))
})
.collect() .collect()
}), }),
&meta_field.arguments, &meta_field.arguments,

View file

@ -49,7 +49,6 @@ pub enum TypeKind {
/// ## Input objects /// ## Input objects
/// ///
/// Represents complex values provided in queries _into_ the system. /// Represents complex values provided in queries _into_ the system.
#[graphql(name = "INPUT_OBJECT")]
InputObject, InputObject,
/// ## List types /// ## List types
@ -63,7 +62,6 @@ pub enum TypeKind {
/// ///
/// 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")]
NonNull, NonNull,
} }
@ -89,7 +87,7 @@ impl<'a, S> Arguments<'a, S> {
if let (Some(args), Some(meta_args)) = (&mut args, meta_args) { if let (Some(args), Some(meta_args)) = (&mut args, meta_args) {
for arg in meta_args { for arg in meta_args {
let arg_name = arg.name.as_str(); let arg_name = arg.name.as_str();
if args.get(arg_name).map_or(true, InputValue::is_null) { if args.get(arg_name).is_none() {
if let Some(val) = arg.default_value.as_ref() { if let Some(val) = arg.default_value.as_ref() {
args.insert(arg_name, val.clone()); args.insert(arg_name, val.clone());
} }
@ -474,8 +472,8 @@ where
f.arguments.as_ref().map(|m| { f.arguments.as_ref().map(|m| {
m.item m.item
.iter() .iter()
.map(|&(ref k, ref v)| { .filter_map(|&(ref k, ref v)| {
(k.item, v.item.clone().into_const(exec_vars)) v.item.clone().into_const(exec_vars).map(|v| (k.item, v))
}) })
.collect() .collect()
}), }),
@ -608,7 +606,7 @@ where
.arguments .arguments
.iter() .iter()
.flat_map(|m| m.item.get("if")) .flat_map(|m| m.item.get("if"))
.flat_map(|v| v.item.clone().into_const(vars).convert()) .filter_map(|v| v.item.clone().into_const(vars)?.convert().ok())
.next() .next()
.unwrap(); .unwrap();

View file

@ -316,7 +316,9 @@ where
f.arguments.as_ref().map(|m| { f.arguments.as_ref().map(|m| {
m.item m.item
.iter() .iter()
.map(|&(ref k, ref v)| (k.item, v.item.clone().into_const(exec_vars))) .filter_map(|&(ref k, ref v)| {
v.item.clone().into_const(exec_vars).map(|v| (k.item, v))
})
.collect() .collect()
}), }),
&meta_field.arguments, &meta_field.arguments,

View file

@ -71,7 +71,7 @@ where
let mut remaining_required_fields = input_fields let mut remaining_required_fields = input_fields
.iter() .iter()
.filter_map(|f| { .filter_map(|f| {
if f.arg_type.is_non_null() { if f.arg_type.is_non_null() && f.default_value.is_none() {
Some(&f.name) Some(&f.name)
} else { } else {
None None

View file

@ -26,6 +26,7 @@ where
{ {
for meta_arg in meta_args { for meta_arg in meta_args {
if meta_arg.arg_type.is_non_null() if meta_arg.arg_type.is_non_null()
&& meta_arg.default_value.is_none()
&& field && field
.item .item
.arguments .arguments

View file

@ -57,9 +57,11 @@ impl ToTokens for Value {
match self { match self {
Self::Default => quote! { Self::Default => quote! {
::std::default::Default::default() ::std::default::Default::default()
},
Self::Expr(expr) => quote! {
(#expr).into()
},
} }
.to_tokens(into), .to_tokens(into)
Self::Expr(expr) => expr.to_tokens(into),
}
} }
} }

View file

@ -1,6 +1,9 @@
//! Tests for `#[derive(GraphQLInputObject)]` macro. //! Tests for `#[derive(GraphQLInputObject)]` macro.
use juniper::{execute, graphql_object, graphql_value, graphql_vars, GraphQLInputObject}; use juniper::{
execute, graphql_object, graphql_value, graphql_vars, parser::SourcePosition, GraphQLError,
GraphQLInputObject, RuleError,
};
use crate::util::schema; use crate::util::schema;
@ -148,16 +151,56 @@ mod default_value {
#[tokio::test] #[tokio::test]
async fn resolves() { async fn resolves() {
const DOC: &str = r#"{ const DOC: &str = r#"query q($ve_num: Float!) {
x(point: { y: 20 }) literal_implicit_other_number: x(point: { y: 20 })
x2: x(point: { x: 20 }) literal_explicit_number: x(point: { x: 20 })
literal_implicit_all: x(point: {})
variable_explicit_number: x(point: { x: $ve_num })
}"#; }"#;
let schema = schema(QueryRoot); let schema = schema(QueryRoot);
assert_eq!(
execute(DOC, None, &schema, &graphql_vars! {"ve_num": 40}, &()).await,
Ok((
graphql_value!({
"literal_implicit_other_number": 10.0,
"literal_explicit_number": 20.0,
"literal_implicit_all": 10.0,
"variable_explicit_number": 40.0,
}),
vec![],
)),
);
}
#[tokio::test]
async fn errs_on_explicit_null_literal() {
const DOC: &str = r#"{ x(point: { x: 20, y: null }) }"#;
let schema = schema(QueryRoot);
assert_eq!( assert_eq!(
execute(DOC, None, &schema, &graphql_vars! {}, &()).await, execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
Ok((graphql_value!({"x": 10.0, "x2": 20.0}), vec![])), Err(GraphQLError::ValidationError(vec![RuleError::new(
"Invalid value for argument \"point\", expected type \"Point2D!\"",
&[SourcePosition::new(11, 0, 11)],
)]))
);
}
#[tokio::test]
async fn errs_on_missing_variable() {
const DOC: &str = r#"query q($x: Float!){ x(point: { x: $x }) }"#;
let schema = schema(QueryRoot);
assert_eq!(
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
Err(GraphQLError::ValidationError(vec![RuleError::new(
"Variable \"$x\" of required type \"Float!\" was not provided.",
&[SourcePosition::new(8, 0, 8)],
)]))
); );
} }
@ -196,6 +239,140 @@ mod default_value {
let schema = schema(QueryRoot); let schema = schema(QueryRoot);
assert_eq!(
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
Ok((
graphql_value!({"__type": {"inputFields": [{
"name": "x",
"description": null,
"type": {"ofType": {"name": "Float"}},
"defaultValue": "10",
}, {
"name": "y",
"description": null,
"type": {"ofType": {"name": "Float"}},
"defaultValue": "10",
}]}}),
vec![],
)),
);
}
}
mod default_nullable_value {
use super::*;
#[derive(GraphQLInputObject)]
struct Point2D {
#[graphql(default = 10.0)]
x: Option<f64>,
#[graphql(default = 10.0)]
y: Option<f64>,
}
struct QueryRoot;
#[graphql_object]
impl QueryRoot {
fn x(point: Point2D) -> Option<f64> {
point.x
}
}
#[tokio::test]
async fn resolves() {
const DOC: &str = r#"query q(
$ve_num: Float,
$ve_null: Float,
$vi: Float,
$vde_num: Float = 40,
$vde_null: Float = 50,
$vdi: Float = 60,
) {
literal_implicit_other_number: x(point: { y: 20 })
literal_explicit_number: x(point: { x: 20 })
literal_implicit_all: x(point: {})
literal_explicit_null: x(point: { x: null })
literal_implicit_other_null: x(point: { y: null })
variable_explicit_number: x(point: { x: $ve_num })
variable_explicit_null: x(point: { x: $ve_null })
variable_implicit: x(point: { x: $vi })
variable_default_explicit_number: x(point: { x: $vde_num })
variable_default_explicit_null: x(point: { x: $vde_null })
variable_default_implicit: x(point: { x: $vdi })
}"#;
let schema = schema(QueryRoot);
assert_eq!(
execute(
DOC,
None,
&schema,
&graphql_vars! {
"ve_num": 30.0,
"ve_null": null,
"vde_num": 100,
"vde_null": null,
},
&(),
)
.await,
Ok((
graphql_value!({
"literal_implicit_other_number": 10.0,
"literal_explicit_number": 20.0,
"literal_implicit_all": 10.0,
"literal_explicit_null": null,
"literal_implicit_other_null": 10.0,
"variable_explicit_number": 30.0,
"variable_explicit_null": null,
"variable_implicit": 10.0,
"variable_default_explicit_number": 100.0,
"variable_default_explicit_null": null,
"variable_default_implicit": 60.0,
}),
vec![],
)),
);
}
#[tokio::test]
async fn is_graphql_input_object() {
const DOC: &str = r#"{
__type(name: "Point2D") {
kind
}
}"#;
let schema = schema(QueryRoot);
assert_eq!(
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
Ok((graphql_value!({"__type": {"kind": "INPUT_OBJECT"}}), vec![])),
);
}
#[tokio::test]
async fn has_input_fields() {
const DOC: &str = r#"{
__type(name: "Point2D") {
inputFields {
name
description
type {
name
ofType {
name
}
}
defaultValue
}
}
}"#;
let schema = schema(QueryRoot);
assert_eq!( assert_eq!(
execute(DOC, None, &schema, &graphql_vars! {}, &()).await, execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
Ok(( Ok((
@ -203,13 +380,13 @@ mod default_value {
{ {
"name": "x", "name": "x",
"description": null, "description": null,
"type": {"ofType": null}, "type": {"name": "Float", "ofType": null},
"defaultValue": "10", "defaultValue": "10",
}, },
{ {
"name": "y", "name": "y",
"description": null, "description": null,
"type": {"ofType": null}, "type": {"name": "Float", "ofType": null},
"defaultValue": "10", "defaultValue": "10",
}, },
]}}), ]}}),

View file

@ -1311,21 +1311,21 @@ mod default_argument {
"args": [{ "args": [{
"name": "first", "name": "first",
"defaultValue": r#""""#, "defaultValue": r#""""#,
"type": {"name": "String", "ofType": null}, "type": {"name": null, "ofType": {"name": "String"}},
}, { }, {
"name": "second", "name": "second",
"defaultValue": r#""second""#, "defaultValue": r#""second""#,
"type": {"name": "String", "ofType": null}, "type": {"name": null, "ofType": {"name": "String"}},
}, { }, {
"name": "third", "name": "third",
"defaultValue": r#""t""#, "defaultValue": r#""t""#,
"type": {"name": "String", "ofType": null}, "type": {"name": null, "ofType": {"name": "String"}},
}], }],
}, { }, {
"args": [{ "args": [{
"name": "coord", "name": "coord",
"defaultValue": "{x: 1}", "defaultValue": "{x: 1}",
"type": {"name": "Point", "ofType": null}, "type": {"name": null, "ofType": {"name": "Point"}},
}], }],
}]}}), }]}}),
vec![], vec![],

View file

@ -1045,10 +1045,10 @@ mod default_argument {
impl Human { impl Human {
fn id( fn id(
#[graphql(default)] arg1: i32, #[graphql(default)] arg1: i32,
#[graphql(default = "second".to_string())] arg2: String, #[graphql(default = "second".to_string())] arg2: Option<String>,
#[graphql(default = true)] r#arg3: bool, #[graphql(default = true)] r#arg3: bool,
) -> String { ) -> String {
format!("{}|{}&{}", arg1, arg2, r#arg3) format!("{}|{:?}&{}", arg1, arg2, r#arg3)
} }
fn info(#[graphql(default = Point { x: 1 })] coord: Point) -> i32 { fn info(#[graphql(default = Point { x: 1 })] coord: Point) -> i32 {
@ -1069,20 +1069,72 @@ mod default_argument {
async fn resolves_id_field() { async fn resolves_id_field() {
let schema = schema(QueryRoot); let schema = schema(QueryRoot);
for (input, expected) in &[ for (input, expected, vars) in &[
("{ human { id } }", "0|second&true"), (
("{ human { id(arg1: 1) } }", "1|second&true"), "{ human { id } }",
(r#"{ human { id(arg2: "") } }"#, "0|&true"), r#"0|Some("second")&true"#,
(r#"{ human { id(arg1: 2, arg2: "") } }"#, "2|&true"), graphql_vars! {},
),
(
"{ human { id(arg1: 1) } }",
r#"1|Some("second")&true"#,
graphql_vars! {},
),
(
r#"{ human { id(arg2: "other") } }"#,
r#"0|Some("other")&true"#,
graphql_vars! {},
),
(
"{ human { id(arg2: null) } }",
r#"0|None&true"#,
graphql_vars! {},
),
(
"query q($arg2: String) { human { id(arg2: $arg2) } }",
r#"0|Some("second")&true"#,
graphql_vars! {},
),
(
"query q($arg2: String) { human{ id(arg2: $arg2) } }",
r#"0|None&true"#,
graphql_vars! { "arg2": null },
),
(
"query q($arg2: String) { human{ id(arg2: $arg2) } }",
r#"0|Some("other")&true"#,
graphql_vars! { "arg2": "other" },
),
(
r#"query q($arg2: String = "other") { human { id(arg2: $arg2) } }"#,
r#"0|Some("other")&true"#,
graphql_vars! {},
),
(
r#"query q($arg2: String = "other") { human { id(arg2: $arg2) } }"#,
r#"0|None&true"#,
graphql_vars! { "arg2": null },
),
(
r#"query q($arg2: String = "other") { human { id(arg2: $arg2) } }"#,
r#"0|Some("hello")&true"#,
graphql_vars! { "arg2": "hello" },
),
(
r#"{ human { id(arg1: 2, arg2: "") } }"#,
r#"2|Some("")&true"#,
graphql_vars! {},
),
( (
r#"{ human { id(arg1: 1, arg2: "", arg3: false) } }"#, r#"{ human { id(arg1: 1, arg2: "", arg3: false) } }"#,
"1|&false", r#"1|Some("")&false"#,
graphql_vars! {},
), ),
] { ] {
let expected: &str = *expected; let expected: &str = *expected;
assert_eq!( assert_eq!(
execute(*input, None, &schema, &graphql_vars! {}, &()).await, execute(*input, None, &schema, &vars, &(),).await,
Ok((graphql_value!({"human": {"id": expected}}), vec![])), Ok((graphql_value!({"human": {"id": expected}}), vec![])),
); );
} }
@ -1133,7 +1185,7 @@ mod default_argument {
"args": [{ "args": [{
"name": "arg1", "name": "arg1",
"defaultValue": "0", "defaultValue": "0",
"type": {"name": "Int", "ofType": null}, "type": {"name": null, "ofType": {"name": "Int"}},
}, { }, {
"name": "arg2", "name": "arg2",
"defaultValue": r#""second""#, "defaultValue": r#""second""#,
@ -1141,13 +1193,13 @@ mod default_argument {
}, { }, {
"name": "arg3", "name": "arg3",
"defaultValue": "true", "defaultValue": "true",
"type": {"name": "Boolean", "ofType": null}, "type": {"name": null, "ofType": {"name": "Boolean"}},
}], }],
}, { }, {
"args": [{ "args": [{
"name": "coord", "name": "coord",
"defaultValue": "{x: 1}", "defaultValue": "{x: 1}",
"type": {"name": "Point", "ofType": null}, "type": {"name": null, "ofType": {"name": "Point"}},
}], }],
}]}}), }]}}),
vec![], vec![],

View file

@ -607,21 +607,21 @@ mod default_argument {
"args": [{ "args": [{
"name": "arg1", "name": "arg1",
"defaultValue": "0", "defaultValue": "0",
"type": {"name": "Int", "ofType": null}, "type": {"name": null, "ofType": {"name": "Int"}},
}, { }, {
"name": "arg2", "name": "arg2",
"defaultValue": r#""second""#, "defaultValue": r#""second""#,
"type": {"name": "String", "ofType": null}, "type": {"name": null, "ofType": {"name": "String"}},
}, { }, {
"name": "arg3", "name": "arg3",
"defaultValue": "true", "defaultValue": "true",
"type": {"name": "Boolean", "ofType": null}, "type": {"name": null, "ofType": {"name": "Boolean"}},
}], }],
}, { }, {
"args": [{ "args": [{
"name": "coord", "name": "coord",
"defaultValue": "{x: 1}", "defaultValue": "{x: 1}",
"type": {"name": "Point", "ofType": null}, "type": {"name": null, "ofType": {"name": "Point"}},
}], }],
}]}}), }]}}),
vec![], vec![],