parent
5332db0a4b
commit
0c8bcf582f
18 changed files with 366 additions and 72 deletions
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>) {
|
||||||
|
|
|
@ -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",
|
||||||
})));
|
})));
|
||||||
})
|
})
|
||||||
|
|
|
@ -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": {
|
||||||
|
|
|
@ -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"#)),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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",
|
||||||
},
|
},
|
||||||
]}}),
|
]}}),
|
||||||
|
|
|
@ -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![],
|
||||||
|
|
|
@ -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![],
|
||||||
|
|
|
@ -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![],
|
||||||
|
|
Loading…
Reference in a new issue