Refactor FromInputValue to return Result instead of Option (#987)

- propagate `FromInputValue` conversion errors during validation
- replace panics with errors during resolving

Co-authored-by: ilslv <ilya.solovyiov@gmail.com>
This commit is contained in:
Kai Ren 2021-12-14 19:30:27 +02:00 committed by GitHub
parent e264cf509d
commit 46be97ada4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
96 changed files with 1305 additions and 837 deletions

View file

@ -113,7 +113,7 @@ The example below is used just for illustration.
# }
# }
# }
#
use juniper::{Value, ParseScalarResult, ParseScalarValue};
use date::Date;
@ -128,10 +128,11 @@ where
}
// Define how to parse a primitive type into your custom scalar.
fn from_input_value(v: &InputValue) -> Option<Date> {
v.as_scalar_value()
.and_then(|v| v.as_str())
.and_then(|s| s.parse().ok())
// NOTE: The error type should implement `IntoFieldError<S>`.
fn from_input_value(v: &InputValue) -> Result<Date, String> {
v.as_string_value()
.ok_or_else(|| format!("Expected `String`, found: {}", v))
.and_then(|s| s.parse().map_err(|e| format!("Failed to parse `Date`: {}", e)))
}
// Define how to parse a string value.
@ -139,6 +140,6 @@ where
<String as ParseScalarValue<S>>::from_str(value)
}
}
#
# fn main() {}
```

View file

@ -70,11 +70,18 @@ impl Query {
}
}
#[tokio::test]
async fn async_simple() {
let schema = RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new());
let doc = r#"
query {
fn main() {}
#[cfg(test)]
mod tests {
use juniper::{graphql_value, EmptyMutation, EmptySubscription, GraphQLError, RootNode, Value};
use super::Query;
#[tokio::test]
async fn async_simple() {
let schema = RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new());
let doc = r#"query {
fieldSync
fieldAsyncPlain
delayed
@ -83,39 +90,37 @@ async fn async_simple() {
name
delayed
}
}
"#;
}"#;
let vars = Default::default();
let (res, errs) = juniper::execute(doc, None, &schema, &vars, &())
.await
.unwrap();
let vars = Default::default();
let (res, errs) = juniper::execute(doc, None, &schema, &vars, &())
.await
.unwrap();
assert!(errs.is_empty());
assert!(errs.is_empty());
let obj = res.into_object().unwrap();
let value = Value::Object(obj);
let obj = res.into_object().unwrap();
let value = Value::Object(obj);
assert_eq!(
value,
graphql_value!({
"delayed": true,
"fieldAsyncPlain": "field_async_plain",
"fieldSync": "field_sync",
"user": {
assert_eq!(
value,
graphql_value!({
"delayed": true,
"kind": "USER",
"name": "user1",
},
}),
);
}
"fieldAsyncPlain": "field_async_plain",
"fieldSync": "field_sync",
"user": {
"delayed": true,
"kind": "USER",
"name": "user1",
},
}),
);
}
#[tokio::test]
async fn async_field_validation_error() {
let schema = RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new());
let doc = r#"
query {
#[tokio::test]
async fn async_field_validation_error() {
let schema = RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new());
let doc = r#"query {
nonExistentField
fieldSync
fieldAsyncPlain
@ -125,40 +130,38 @@ async fn async_field_validation_error() {
name
delayed
}
}
"#;
}"#;
let vars = Default::default();
let result = juniper::execute(doc, None, &schema, &vars, &()).await;
assert!(result.is_err());
let vars = Default::default();
let result = juniper::execute(doc, None, &schema, &vars, &()).await;
assert!(result.is_err());
let error = result.err().unwrap();
let is_validation_error = match error {
GraphQLError::ValidationError(_) => true,
_ => false,
};
assert!(is_validation_error);
let error = result.err().unwrap();
let is_validation_error = match error {
GraphQLError::ValidationError(_) => true,
_ => false,
};
assert!(is_validation_error);
}
// FIXME: test seems broken by design, re-enable later
// #[tokio::test]
// async fn resolve_into_stream_validation_error() {
// let schema = RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new());
// let doc = r#"
// subscription {
// nonExistent
// }
// "#;
// let vars = Default::default();
// let result = juniper::resolve_into_stream(doc, None, &schema, &vars, &()).await;
// assert!(result.is_err());
// let error = result.err().unwrap();
// let is_validation_error = match error {
// GraphQLError::ValidationError(_) => true,
// _ => false,
// };
// assert!(is_validation_error);
// }
}
// FIXME: test seems broken by design, re-enable later
// #[tokio::test]
// async fn resolve_into_stream_validation_error() {
// let schema = RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new());
// let doc = r#"
// subscription {
// nonExistent
// }
// "#;
// let vars = Default::default();
// let result = juniper::resolve_into_stream(doc, None, &schema, &vars, &()).await;
// assert!(result.is_err());
// let error = result.err().unwrap();
// let is_validation_error = match error {
// GraphQLError::ValidationError(_) => true,
// _ => false,
// };
// assert!(is_validation_error);
// }
fn main() {}

View file

@ -1,7 +1,8 @@
error: GraphQL enum expects at least one field
--> $DIR/derive_no_fields.rs:2:1
= note: https://spec.graphql.org/June2018/#sec-Enums
--> fail/enum/derive_no_fields.rs:2:1
|
2 | pub enum Test {}
| ^^^^^^^^^^^^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Enums
| ^^^

View file

@ -1,14 +1,18 @@
error[E0277]: the trait bound `ObjectA: IsInputType<__S>` is not satisfied
--> $DIR/derive_incompatible_object.rs:6:10
|
6 | #[derive(juniper::GraphQLInputObject)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsInputType<__S>` is not implemented for `ObjectA`
|
= note: required by `juniper::marker::IsInputType::mark`
= note: this error originates in the derive macro `juniper::GraphQLInputObject` (in Nightly builds, run with -Z macro-backtrace for more info)
--> fail/input-object/derive_incompatible_object.rs:6:10
|
6 | #[derive(juniper::GraphQLInputObject)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsInputType<__S>` is not implemented for `ObjectA`
|
note: required by `juniper::marker::IsInputType::mark`
--> $WORKSPACE/juniper/src/types/marker.rs
|
| fn mark() {}
| ^^^^^^^^^
= note: this error originates in the derive macro `juniper::GraphQLInputObject` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0277]: the trait bound `ObjectA: FromInputValue<__S>` is not satisfied
--> $DIR/derive_incompatible_object.rs:6:10
--> fail/input-object/derive_incompatible_object.rs:6:10
|
6 | #[derive(juniper::GraphQLInputObject)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjectA`
@ -16,16 +20,20 @@ error[E0277]: the trait bound `ObjectA: FromInputValue<__S>` is not satisfied
= note: this error originates in the derive macro `juniper::GraphQLInputObject` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0277]: the trait bound `ObjectA: FromInputValue<__S>` is not satisfied
--> $DIR/derive_incompatible_object.rs:6:10
|
6 | #[derive(juniper::GraphQLInputObject)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjectA`
|
= note: required by `from_input_value`
= note: this error originates in the derive macro `juniper::GraphQLInputObject` (in Nightly builds, run with -Z macro-backtrace for more info)
--> fail/input-object/derive_incompatible_object.rs:6:10
|
6 | #[derive(juniper::GraphQLInputObject)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjectA`
|
note: required by `from_input_value`
--> $WORKSPACE/juniper/src/ast.rs
|
| fn from_input_value(v: &InputValue<S>) -> Result<Self, Self::Error>;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: this error originates in the derive macro `juniper::GraphQLInputObject` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0599]: no method named `to_input_value` found for struct `ObjectA` in the current scope
--> $DIR/derive_incompatible_object.rs:6:10
--> fail/input-object/derive_incompatible_object.rs:6:10
|
2 | struct ObjectA {
| -------------- method `to_input_value` not found for this

View file

@ -1,7 +1,8 @@
error: GraphQL input object expects at least one field
--> $DIR/derive_no_fields.rs:2:1
= note: https://spec.graphql.org/June2018/#sec-Input-Objects
--> fail/input-object/derive_no_fields.rs:2:1
|
2 | struct Object {}
| ^^^^^^^^^^^^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Input-Objects
| ^^^^^^

View file

@ -1,7 +1,8 @@
error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQLs introspection system.
--> $DIR/derive_no_underscore.rs:3:15
= note: https://spec.graphql.org/June2018/#sec-Schema
--> fail/input-object/derive_no_underscore.rs:3:15
|
3 | #[graphql(name = "__test")]
| ^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Schema

View file

@ -1,9 +1,9 @@
error: GraphQL input object does not allow fields with the same name
--> $DIR/derive_unique_name.rs:4:5
|
4 | / #[graphql(name = "test")]
5 | | test2: String,
| |_________________^
|
= help: There is at least one other field with the same name `test`, possibly renamed via the #[graphql] attribute
= note: https://spec.graphql.org/June2018/#sec-Input-Objects
--> fail/input-object/derive_unique_name.rs:4:5
|
4 | #[graphql(name = "test")]
| ^

View file

@ -1,10 +1,11 @@
error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQLs introspection system.
= note: https://spec.graphql.org/June2018/#sec-Schema
--> $DIR/argument_double_underscored.rs:14:18
|
14 | fn id(&self, __num: i32) -> &str {
| ^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Schema
error[E0412]: cannot find type `CharacterValue` in this scope
--> $DIR/argument_double_underscored.rs:4:18

View file

@ -1,14 +1,18 @@
error[E0277]: the trait bound `ObjA: IsInputType<__S>` is not satisfied
--> $DIR/argument_non_input_type.rs:16:1
|
16 | #[graphql_interface(for = ObjA)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsInputType<__S>` is not implemented for `ObjA`
|
= note: required by `juniper::marker::IsInputType::mark`
= note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info)
--> fail/interface/argument_non_input_type.rs:16:1
|
16 | #[graphql_interface(for = ObjA)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsInputType<__S>` is not implemented for `ObjA`
|
note: required by `juniper::marker::IsInputType::mark`
--> $WORKSPACE/juniper/src/types/marker.rs
|
| fn mark() {}
| ^^^^^^^^^
= note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0277]: the trait bound `ObjA: FromInputValue<__S>` is not satisfied
--> $DIR/argument_non_input_type.rs:16:1
--> fail/interface/argument_non_input_type.rs:16:1
|
16 | #[graphql_interface(for = ObjA)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjA`

View file

@ -1,11 +1,12 @@
error: GraphQL interface trait method `as_obja` conflicts with the external downcast function `downcast_obja` declared on the trait to downcast into the implementer type `ObjA`
= note: https://spec.graphql.org/June2018/#sec-Interfaces
= note: use `#[graphql(ignore)]` attribute argument to ignore this trait method for interface implementers downcasting
--> $DIR/downcast_method_conflicts_with_external_downcast_fn.rs:26:5
|
26 | fn as_obja(&self) -> Option<&ObjA>;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Interfaces
= note: use `#[graphql(ignore)]` attribute argument to ignore this trait method for interface implementers downcasting
| ^^
error[E0412]: cannot find type `CharacterValue` in this scope
--> $DIR/downcast_method_conflicts_with_external_downcast_fn.rs:4:18

View file

@ -1,10 +1,11 @@
error: GraphQL interface expects trait method to accept `&self` only and, optionally, `&Context`
= note: https://spec.graphql.org/June2018/#sec-Interfaces
--> $DIR/downcast_method_wrong_input_args.rs:10:10
|
10 | fn a(&self, ctx: &(), rand: u8) -> Option<&Human> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Interfaces
| ^
error[E0412]: cannot find type `CharacterValue` in this scope
--> $DIR/downcast_method_wrong_input_args.rs:16:18

View file

@ -1,19 +1,20 @@
error: GraphQL interface expects trait method return type to be `Option<&ImplementerType>` only
--> $DIR/downcast_method_wrong_return_type.rs:10:40
= note: https://spec.graphql.org/June2018/#sec-Interfaces
--> fail/interface/downcast_method_wrong_return_type.rs:10:40
|
10 | fn a(&self, ctx: &(), rand: u8) -> &Human {
| ^^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Interfaces
| ^
error[E0412]: cannot find type `CharacterValue` in this scope
--> $DIR/downcast_method_wrong_return_type.rs:16:18
--> fail/interface/downcast_method_wrong_return_type.rs:16:18
|
16 | #[graphql(impl = CharacterValue)]
| ^^^^^^^^^^^^^^ not found in this scope
error[E0405]: cannot find trait `Character` in this scope
--> $DIR/downcast_method_wrong_return_type.rs:22:6
--> fail/interface/downcast_method_wrong_return_type.rs:22:6
|
22 | impl Character for Human {}
| ^^^^^^^^^ not found in this scope

View file

@ -1,19 +1,20 @@
error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQLs introspection system.
--> $DIR/field_double_underscored.rs:14:8
= note: https://spec.graphql.org/June2018/#sec-Schema
--> fail/interface/field_double_underscored.rs:14:8
|
14 | fn __id(&self) -> &str {
| ^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Schema
error[E0412]: cannot find type `CharacterValue` in this scope
--> $DIR/field_double_underscored.rs:4:18
--> fail/interface/field_double_underscored.rs:4:18
|
4 | #[graphql(impl = CharacterValue)]
| ^^^^^^^^^^^^^^ not found in this scope
error[E0405]: cannot find trait `Character` in this scope
--> $DIR/field_double_underscored.rs:10:6
--> fail/interface/field_double_underscored.rs:10:6
|
10 | impl Character for ObjA {}
| ^^^^^^^^^ not found in this scope

View file

@ -1,8 +1,12 @@
error[E0277]: the trait bound `ObjB: IsOutputType<__S>` is not satisfied
--> $DIR/field_non_output_return_type.rs:17:1
|
17 | #[graphql_interface(for = ObjA)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB`
|
= note: required by `juniper::marker::IsOutputType::mark`
= note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info)
--> fail/interface/field_non_output_return_type.rs:17:1
|
17 | #[graphql_interface(for = ObjA)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB`
|
note: required by `juniper::marker::IsOutputType::mark`
--> $WORKSPACE/juniper/src/types/marker.rs
|
| fn mark() {}
| ^^^^^^^^^
= note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -1,25 +1,20 @@
error: GraphQL interface must have a different name for each field
--> $DIR/fields_duplicate.rs:13:1
= note: https://spec.graphql.org/June2018/#sec-Interfaces
--> fail/interface/fields_duplicate.rs:13:1
|
13 | / trait Character {
14 | | fn id(&self) -> &str {
15 | | "funA"
16 | | }
... |
21 | | }
22 | | }
| |_^
|
= note: https://spec.graphql.org/June2018/#sec-Interfaces
13 | trait Character {
| ^^^^^
error[E0412]: cannot find type `CharacterValue` in this scope
--> $DIR/fields_duplicate.rs:4:18
--> fail/interface/fields_duplicate.rs:4:18
|
4 | #[graphql(impl = CharacterValue)]
| ^^^^^^^^^^^^^^ not found in this scope
error[E0405]: cannot find trait `Character` in this scope
--> $DIR/fields_duplicate.rs:10:6
--> fail/interface/fields_duplicate.rs:10:6
|
10 | impl Character for ObjA {}
| ^^^^^^^^^ not found in this scope

View file

@ -1,17 +1,25 @@
error[E0277]: the trait bound `ObjA: GraphQLObject<__S>` is not satisfied
--> $DIR/implementer_non_object_type.rs:15:1
--> fail/interface/implementer_non_object_type.rs:15:1
|
15 | #[graphql_interface(for = ObjA)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `GraphQLObject<__S>` is not implemented for `ObjA`
|
= note: required by `juniper::GraphQLObject::mark`
note: required by `juniper::GraphQLObject::mark`
--> $WORKSPACE/juniper/src/types/marker.rs
|
| fn mark() {}
| ^^^^^^^^^
= note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0277]: the trait bound `ObjA: IsOutputType<__S>` is not satisfied
--> $DIR/implementer_non_object_type.rs:15:1
|
15 | #[graphql_interface(for = ObjA)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjA`
|
= note: required by `juniper::marker::IsOutputType::mark`
= note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info)
--> fail/interface/implementer_non_object_type.rs:15:1
|
15 | #[graphql_interface(for = ObjA)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjA`
|
note: required by `juniper::marker::IsOutputType::mark`
--> $WORKSPACE/juniper/src/types/marker.rs
|
| fn mark() {}
| ^^^^^^^^^
= note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -1,7 +1,8 @@
error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQLs introspection system.
= note: https://spec.graphql.org/June2018/#sec-Schema
--> $DIR/name_double_underscored.rs:4:7
|
4 | trait __Character {
| ^^^^^^^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Schema

View file

@ -1,10 +1,11 @@
error: GraphQL interface must have at least one field
= note: https://spec.graphql.org/June2018/#sec-Interfaces
--> $DIR/no_fields.rs:13:1
|
13 | trait Character {}
| ^^^^^^^^^^^^^^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Interfaces
| ^^^^^
error[E0412]: cannot find type `CharacterValue` in this scope
--> $DIR/no_fields.rs:4:18

View file

@ -1,7 +1,8 @@
error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQLs introspection system.
--> $DIR/argument_double_underscored.rs:7:18
= note: https://spec.graphql.org/June2018/#sec-Schema
--> fail/object/argument_double_underscored.rs:7:18
|
7 | fn id(&self, __num: i32) -> &str {
| ^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Schema

View file

@ -1,14 +1,18 @@
error[E0277]: the trait bound `ObjA: IsInputType<__S>` is not satisfied
--> $DIR/argument_non_input_type.rs:10:1
|
10 | #[graphql_object]
| ^^^^^^^^^^^^^^^^^ the trait `IsInputType<__S>` is not implemented for `ObjA`
|
= note: required by `juniper::marker::IsInputType::mark`
= note: this error originates in the attribute macro `graphql_object` (in Nightly builds, run with -Z macro-backtrace for more info)
--> fail/object/argument_non_input_type.rs:10:1
|
10 | #[graphql_object]
| ^^^^^^^^^^^^^^^^^ the trait `IsInputType<__S>` is not implemented for `ObjA`
|
note: required by `juniper::marker::IsInputType::mark`
--> $WORKSPACE/juniper/src/types/marker.rs
|
| fn mark() {}
| ^^^^^^^^^
= note: this error originates in the attribute macro `graphql_object` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0277]: the trait bound `ObjA: FromInputValue<__S>` is not satisfied
--> $DIR/argument_non_input_type.rs:10:1
--> fail/object/argument_non_input_type.rs:10:1
|
10 | #[graphql_object]
| ^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjA`

View file

@ -1,8 +1,12 @@
error[E0277]: the trait bound `ObjB: IsOutputType<__S>` is not satisfied
--> $DIR/attr_field_non_output_return_type.rs:10:1
|
10 | #[graphql_object]
| ^^^^^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB`
|
= note: required by `juniper::marker::IsOutputType::mark`
= note: this error originates in the attribute macro `graphql_object` (in Nightly builds, run with -Z macro-backtrace for more info)
--> fail/object/attr_field_non_output_return_type.rs:10:1
|
10 | #[graphql_object]
| ^^^^^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB`
|
note: required by `juniper::marker::IsOutputType::mark`
--> $WORKSPACE/juniper/src/types/marker.rs
|
| fn mark() {}
| ^^^^^^^^^
= note: this error originates in the attribute macro `graphql_object` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -1,7 +1,8 @@
error: GraphQL object must have a different name for each field
--> $DIR/attr_fields_duplicate.rs:6:6
= note: https://spec.graphql.org/June2018/#sec-Objects
--> fail/object/attr_fields_duplicate.rs:6:6
|
6 | impl ObjA {
| ^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Objects

View file

@ -1,7 +1,8 @@
error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQLs introspection system.
--> $DIR/attr_name_double_underscored.rs:6:6
= note: https://spec.graphql.org/June2018/#sec-Schema
--> fail/object/attr_name_double_underscored.rs:6:6
|
6 | impl __Obj {
| ^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Schema

View file

@ -1,7 +1,8 @@
error: GraphQL object must have at least one field
--> $DIR/attr_no_fields.rs:6:6
= note: https://spec.graphql.org/June2018/#sec-Objects
--> fail/object/attr_no_fields.rs:6:6
|
6 | impl Obj {}
| ^^^
|
= note: https://spec.graphql.org/June2018/#sec-Objects

View file

@ -1,7 +1,8 @@
error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQLs introspection system.
--> $DIR/derive_field_double_underscored.rs:5:5
= note: https://spec.graphql.org/June2018/#sec-Schema
--> fail/object/derive_field_double_underscored.rs:5:5
|
5 | __test: String,
| ^^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Schema

View file

@ -1,8 +1,12 @@
error[E0277]: the trait bound `ObjB: IsOutputType<__S>` is not satisfied
--> $DIR/derive_field_non_output_return_type.rs:8:10
|
8 | #[derive(GraphQLObject)]
| ^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB`
|
= note: required by `juniper::marker::IsOutputType::mark`
= note: this error originates in the derive macro `GraphQLObject` (in Nightly builds, run with -Z macro-backtrace for more info)
--> fail/object/derive_field_non_output_return_type.rs:8:10
|
8 | #[derive(GraphQLObject)]
| ^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB`
|
note: required by `juniper::marker::IsOutputType::mark`
--> $WORKSPACE/juniper/src/types/marker.rs
|
| fn mark() {}
| ^^^^^^^^^
= note: this error originates in the derive macro `GraphQLObject` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -1,11 +1,8 @@
error: GraphQL object must have a different name for each field
--> $DIR/derive_fields_duplicate.rs:4:1
|
4 | / struct ObjA {
5 | | id: String,
6 | | #[graphql(name = "id")]
7 | | id2: String,
8 | | }
| |_^
|
= note: https://spec.graphql.org/June2018/#sec-Objects
--> fail/object/derive_fields_duplicate.rs:4:1
|
4 | struct ObjA {
| ^^^^^^

View file

@ -1,7 +1,8 @@
error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQLs introspection system.
--> $DIR/derive_name_double_underscored.rs:4:8
= note: https://spec.graphql.org/June2018/#sec-Schema
--> fail/object/derive_name_double_underscored.rs:4:8
|
4 | struct __Obj {
| ^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Schema

View file

@ -1,7 +1,8 @@
error: GraphQL object must have at least one field
--> $DIR/derive_no_fields.rs:4:1
= note: https://spec.graphql.org/June2018/#sec-Objects
--> fail/object/derive_no_fields.rs:4:1
|
4 | struct Obj {}
| ^^^^^^^^^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Objects
| ^^^^^^

View file

@ -1,5 +1,5 @@
error: GraphQL object can only be derived for structs
--> $DIR/derive_wrong_item.rs:4:1
--> fail/object/derive_wrong_item.rs:4:1
|
4 | enum Character {}
| ^^^^^^^^^^^^^^^^^
| ^^^^

View file

@ -1,7 +1,8 @@
error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQLs introspection system.
--> $DIR/argument_double_underscored.rs:11:24
= note: https://spec.graphql.org/June2018/#sec-Schema
--> fail/subscription/argument_double_underscored.rs:11:24
|
11 | async fn id(&self, __num: i32) -> Stream<'static, &'static str> {
| ^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Schema

View file

@ -1,14 +1,18 @@
error[E0277]: the trait bound `ObjA: IsInputType<__S>` is not satisfied
--> $DIR/argument_non_input_type.rs:15:1
|
15 | #[graphql_subscription]
| ^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsInputType<__S>` is not implemented for `ObjA`
|
= note: required by `juniper::marker::IsInputType::mark`
= note: this error originates in the attribute macro `graphql_subscription` (in Nightly builds, run with -Z macro-backtrace for more info)
--> fail/subscription/argument_non_input_type.rs:15:1
|
15 | #[graphql_subscription]
| ^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsInputType<__S>` is not implemented for `ObjA`
|
note: required by `juniper::marker::IsInputType::mark`
--> $WORKSPACE/juniper/src/types/marker.rs
|
| fn mark() {}
| ^^^^^^^^^
= note: this error originates in the attribute macro `graphql_subscription` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0277]: the trait bound `ObjA: FromInputValue<__S>` is not satisfied
--> $DIR/argument_non_input_type.rs:15:1
--> fail/subscription/argument_non_input_type.rs:15:1
|
15 | #[graphql_subscription]
| ^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjA`

View file

@ -1,8 +1,12 @@
error[E0277]: the trait bound `ObjB: IsOutputType<__S>` is not satisfied
--> $DIR/field_non_output_return_type.rs:15:1
|
15 | #[graphql_subscription]
| ^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB`
|
= note: required by `juniper::marker::IsOutputType::mark`
= note: this error originates in the attribute macro `graphql_subscription` (in Nightly builds, run with -Z macro-backtrace for more info)
--> fail/subscription/field_non_output_return_type.rs:15:1
|
15 | #[graphql_subscription]
| ^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB`
|
note: required by `juniper::marker::IsOutputType::mark`
--> $WORKSPACE/juniper/src/types/marker.rs
|
| fn mark() {}
| ^^^^^^^^^
= note: this error originates in the attribute macro `graphql_subscription` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -1,8 +1,9 @@
error: GraphQL object synchronous resolvers are not supported
--> $DIR/field_not_async.rs:11:5
= note: https://spec.graphql.org/June2018/#sec-Objects
= note: Specify that this function is async: `async fn foo()`
--> fail/subscription/field_not_async.rs:11:5
|
11 | fn id(&self) -> Stream<'static, bool> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Objects
= note: Specify that this function is async: `async fn foo()`
| ^^

View file

@ -1,7 +1,8 @@
error: GraphQL object must have a different name for each field
--> $DIR/fields_duplicate.rs:10:6
= note: https://spec.graphql.org/June2018/#sec-Objects
--> fail/subscription/fields_duplicate.rs:10:6
|
10 | impl ObjA {
| ^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Objects

View file

@ -1,7 +1,8 @@
error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQLs introspection system.
--> $DIR/name_double_underscored.rs:10:6
= note: https://spec.graphql.org/June2018/#sec-Schema
--> fail/subscription/name_double_underscored.rs:10:6
|
10 | impl __Obj {
| ^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Schema

View file

@ -1,7 +1,8 @@
error: GraphQL object must have at least one field
--> $DIR/no_fields.rs:6:6
= note: https://spec.graphql.org/June2018/#sec-Objects
--> fail/subscription/no_fields.rs:6:6
|
6 | impl Obj {}
| ^^^
|
= note: https://spec.graphql.org/June2018/#sec-Objects

View file

@ -1,5 +1,5 @@
error: GraphQL union can only be derived for enums and structs
--> $DIR/derive_wrong_item.rs:4:1
--> fail/union/derive_wrong_item.rs:4:1
|
4 | union Character { id: i32 }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
| ^^^^^

View file

@ -1,7 +1,8 @@
error: GraphQL union variant `Human` already has external resolver function `resolve_fn1` declared on the enum
--> $DIR/enum_external_resolver_fn_conflicts_with_variant_external_resolver_fn.rs:6:15
= note: https://spec.graphql.org/June2018/#sec-Unions
--> fail/union/enum_external_resolver_fn_conflicts_with_variant_external_resolver_fn.rs:6:15
|
6 | #[graphql(with = resolve_fn2)]
| ^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Unions

View file

@ -1,7 +1,8 @@
error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQLs introspection system.
--> $DIR/enum_name_double_underscored.rs:4:6
= note: https://spec.graphql.org/June2018/#sec-Schema
--> fail/union/enum_name_double_underscored.rs:4:6
|
4 | enum __Character {
| ^^^^^^^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Schema

View file

@ -1,7 +1,8 @@
error: GraphQL union expects at least one union variant
--> $DIR/enum_no_fields.rs:4:1
= note: https://spec.graphql.org/June2018/#sec-Unions
--> fail/union/enum_no_fields.rs:4:1
|
4 | enum Character {}
| ^^^^^^^^^^^^^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Unions
| ^^^^

View file

@ -1,8 +1,12 @@
error[E0277]: the trait bound `Test: GraphQLObject<__S>` is not satisfied
--> $DIR/enum_non_object_variant.rs:9:10
|
9 | #[derive(GraphQLUnion)]
| ^^^^^^^^^^^^ the trait `GraphQLObject<__S>` is not implemented for `Test`
|
= note: required by `juniper::GraphQLObject::mark`
= note: this error originates in the derive macro `GraphQLUnion` (in Nightly builds, run with -Z macro-backtrace for more info)
--> fail/union/enum_non_object_variant.rs:9:10
|
9 | #[derive(GraphQLUnion)]
| ^^^^^^^^^^^^ the trait `GraphQLObject<__S>` is not implemented for `Test`
|
note: required by `juniper::GraphQLObject::mark`
--> $WORKSPACE/juniper/src/types/marker.rs
|
| fn mark() {}
| ^^^^^^^^^
= note: this error originates in the derive macro `GraphQLUnion` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -1,10 +1,8 @@
error: GraphQL union must have a different type for each union variant
--> $DIR/enum_same_type_pretty.rs:4:1
|
4 | / enum Character {
5 | | A(u8),
6 | | B(u8),
7 | | }
| |_^
|
= note: https://spec.graphql.org/June2018/#sec-Unions
--> fail/union/enum_same_type_pretty.rs:4:1
|
4 | enum Character {
| ^^^^

View file

@ -1,15 +1,17 @@
error: GraphQL union enum allows only unnamed variants with a single field, e.g. `Some(T)`
--> $DIR/enum_wrong_variant_field.rs:5:5
= note: https://spec.graphql.org/June2018/#sec-Unions
--> fail/union/enum_wrong_variant_field.rs:5:5
|
5 | A { human: Human },
| ^
|
= note: https://spec.graphql.org/June2018/#sec-Unions
error: GraphQL union enum allows only unnamed variants with a single field, e.g. `Some(T)`
--> $DIR/enum_wrong_variant_field.rs:10:6
= note: https://spec.graphql.org/June2018/#sec-Unions
--> fail/union/enum_wrong_variant_field.rs:10:6
|
10 | A(Human, u8),
| ^^^^^^^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Unions

View file

@ -1,7 +1,8 @@
error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQLs introspection system.
--> $DIR/struct_name_double_underscored.rs:5:8
= note: https://spec.graphql.org/June2018/#sec-Schema
--> fail/union/struct_name_double_underscored.rs:5:8
|
5 | struct __Character;
| ^^^^^^^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Schema

View file

@ -1,7 +1,8 @@
error: GraphQL union expects at least one union variant
--> $DIR/struct_no_fields.rs:4:1
= note: https://spec.graphql.org/June2018/#sec-Unions
--> fail/union/struct_no_fields.rs:4:1
|
4 | struct Character;
| ^^^^^^^^^^^^^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Unions
| ^^^^^^

View file

@ -1,8 +1,12 @@
error[E0277]: the trait bound `Test: GraphQLObject<__S>` is not satisfied
--> $DIR/struct_non_object_variant.rs:9:10
|
9 | #[derive(GraphQLUnion)]
| ^^^^^^^^^^^^ the trait `GraphQLObject<__S>` is not implemented for `Test`
|
= note: required by `juniper::GraphQLObject::mark`
= note: this error originates in the derive macro `GraphQLUnion` (in Nightly builds, run with -Z macro-backtrace for more info)
--> fail/union/struct_non_object_variant.rs:9:10
|
9 | #[derive(GraphQLUnion)]
| ^^^^^^^^^^^^ the trait `GraphQLObject<__S>` is not implemented for `Test`
|
note: required by `juniper::GraphQLObject::mark`
--> $WORKSPACE/juniper/src/types/marker.rs
|
| fn mark() {}
| ^^^^^^^^^
= note: this error originates in the derive macro `GraphQLUnion` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -1,14 +1,23 @@
error[E0277]: the trait bound `CustomContext: FromContext<SubContext>` is not satisfied
--> $DIR/trait_fail_infer_context.rs:3:1
|
3 | #[graphql_union]
| ^^^^^^^^^^^^^^^^ expected an implementor of trait `FromContext<SubContext>`
|
= note: required by `juniper::FromContext::from`
= note: this error originates in the attribute macro `graphql_union` (in Nightly builds, run with -Z macro-backtrace for more info)
--> fail/union/trait_fail_infer_context.rs:3:1
|
3 | #[graphql_union]
| ^^^^^^^^^^^^^^^^ expected an implementor of trait `FromContext<SubContext>`
4 | trait Character {
| _______-
5 | | fn a(&self, ctx: &SubContext) -> Option<&Human>;
6 | | fn b(&self, ctx: &CustomContext) -> Option<&Droid>;
| |________- required by a bound introduced by this call
|
note: required by `juniper::FromContext::from`
--> $WORKSPACE/juniper/src/executor/mod.rs
|
| fn from(value: &T) -> &Self;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: this error originates in the attribute macro `graphql_union` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0308]: mismatched types
--> $DIR/trait_fail_infer_context.rs:3:1
--> fail/union/trait_fail_infer_context.rs:3:1
|
3 | #[graphql_union]
| ^^^^^^^^^^^^^^^^ expected struct `CustomContext`, found struct `SubContext`

View file

@ -1,8 +1,9 @@
error: GraphQL union trait method `a` conflicts with the external resolver function `some_fn` declared on the trait to resolve the variant type `Human`
--> $DIR/trait_method_conflicts_with_external_resolver_fn.rs:5:5
|
5 | fn a(&self) -> Option<&Human>;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Unions
= note: use `#[graphql(ignore)]` attribute to ignore this trait method for union variants resolution
--> fail/union/trait_method_conflicts_with_external_resolver_fn.rs:5:5
|
5 | fn a(&self) -> Option<&Human>;
| ^^

View file

@ -1,7 +1,8 @@
error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQLs introspection system.
--> $DIR/trait_name_double_underscored.rs:4:7
= note: https://spec.graphql.org/June2018/#sec-Schema
--> fail/union/trait_name_double_underscored.rs:4:7
|
4 | trait __Character {
| ^^^^^^^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Schema

View file

@ -1,7 +1,8 @@
error: GraphQL union expects at least one union variant
--> $DIR/trait_no_fields.rs:4:1
= note: https://spec.graphql.org/June2018/#sec-Unions
--> fail/union/trait_no_fields.rs:4:1
|
4 | trait Character {}
| ^^^^^^^^^^^^^^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Unions
| ^^^^^

View file

@ -1,8 +1,12 @@
error[E0277]: the trait bound `Test: GraphQLObject<__S>` is not satisfied
--> $DIR/trait_non_object_variant.rs:9:1
|
9 | #[graphql_union]
| ^^^^^^^^^^^^^^^^ the trait `GraphQLObject<__S>` is not implemented for `Test`
|
= note: required by `juniper::GraphQLObject::mark`
= note: this error originates in the attribute macro `graphql_union` (in Nightly builds, run with -Z macro-backtrace for more info)
--> fail/union/trait_non_object_variant.rs:9:1
|
9 | #[graphql_union]
| ^^^^^^^^^^^^^^^^ the trait `GraphQLObject<__S>` is not implemented for `Test`
|
note: required by `juniper::GraphQLObject::mark`
--> $WORKSPACE/juniper/src/types/marker.rs
|
| fn mark() {}
| ^^^^^^^^^
= note: this error originates in the attribute macro `graphql_union` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -1,10 +1,8 @@
error: GraphQL union must have a different type for each union variant
--> $DIR/trait_same_type_pretty.rs:4:1
|
4 | / trait Character {
5 | | fn a(&self) -> Option<&u8>;
6 | | fn b(&self) -> Option<&u8>;
7 | | }
| |_^
|
= note: https://spec.graphql.org/June2018/#sec-Unions
--> fail/union/trait_same_type_pretty.rs:4:1
|
4 | trait Character {
| ^^^^^

View file

@ -1,8 +1,9 @@
error: GraphQL union cannot use #[graphql(with = ...)] attribute on a trait method
--> $DIR/trait_with_attr_on_method.rs:5:15
= note: https://spec.graphql.org/June2018/#sec-Unions
= note: instead use #[graphql(ignore)] on the method with #[graphql_union(on ... = ...)] on the trait itself
--> fail/union/trait_with_attr_on_method.rs:5:15
|
5 | #[graphql(with = something)]
| ^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Unions
= note: instead use #[graphql(ignore)] on the method with #[graphql_union(on ... = ...)] on the trait itself

View file

@ -1,7 +1,8 @@
error: GraphQL union expects trait method to accept `&self` only and, optionally, `&Context`
--> $DIR/trait_wrong_method_input_args.rs:5:10
= note: https://spec.graphql.org/June2018/#sec-Unions
--> fail/union/trait_wrong_method_input_args.rs:5:10
|
5 | fn a(&self, ctx: &(), rand: u8) -> Option<&Human>;
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Unions
| ^

View file

@ -1,7 +1,8 @@
error: GraphQL union expects trait method return type to be `Option<&VariantType>` only
--> $DIR/trait_wrong_method_return_type.rs:5:20
= note: https://spec.graphql.org/June2018/#sec-Unions
--> fail/union/trait_wrong_method_return_type.rs:5:20
|
5 | fn a(&self) -> &Human;
| ^^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Unions
| ^

View file

@ -84,7 +84,7 @@ fn test_derived_enum() {
);
assert_eq!(
FromInputValue::<DefaultScalarValue>::from_input_value(&graphql_input_value!(REGULAR)),
Some(SomeEnum::Regular),
Ok(SomeEnum::Regular),
);
// Test FULL variant.
@ -94,7 +94,7 @@ fn test_derived_enum() {
);
assert_eq!(
FromInputValue::<DefaultScalarValue>::from_input_value(&graphql_input_value!(FULL)),
Some(SomeEnum::Full)
Ok(SomeEnum::Full)
);
}

View file

@ -1,7 +1,7 @@
use fnv::FnvHashMap;
use juniper::{
graphql_input_value, marker, DefaultScalarValue, FromInputValue, GraphQLInputObject,
GraphQLType, GraphQLValue, InputValue, Registry, ToInputValue,
graphql_input_value, marker, DefaultScalarValue, FieldError, FromInputValue,
GraphQLInputObject, GraphQLType, GraphQLValue, InputValue, Registry, ToInputValue,
};
#[derive(GraphQLInputObject, Debug, PartialEq)]
@ -58,8 +58,10 @@ struct Fake;
impl<'a> marker::IsInputType<DefaultScalarValue> for &'a Fake {}
impl<'a> FromInputValue for &'a Fake {
fn from_input_value(_v: &InputValue) -> Option<&'a Fake> {
None
type Error = FieldError;
fn from_input_value(_v: &InputValue) -> Result<&'a Fake, Self::Error> {
Err("This is fake".into())
}
}

View file

@ -31,10 +31,10 @@ where
Value::scalar(self.0)
}
fn from_input_value(v: &InputValue) -> Option<DefaultName> {
v.as_scalar_value()
.and_then(|s| s.as_int())
.map(|i| DefaultName(i))
fn from_input_value(v: &InputValue) -> Result<DefaultName, String> {
v.as_int_value()
.map(DefaultName)
.ok_or_else(|| format!("Expected `Int`, found: {}", v))
}
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
@ -48,8 +48,10 @@ impl GraphQLScalar for OtherOrder {
Value::scalar(self.0)
}
fn from_input_value(v: &InputValue) -> Option<OtherOrder> {
v.as_scalar_value::<i32>().map(|i| OtherOrder(*i))
fn from_input_value(v: &InputValue) -> Result<OtherOrder, String> {
v.as_int_value()
.map(OtherOrder)
.ok_or_else(|| format!("Expected `Int`, found: {}", v))
}
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, DefaultScalarValue> {
@ -63,8 +65,10 @@ impl GraphQLScalar for Named {
Value::scalar(self.0)
}
fn from_input_value(v: &InputValue) -> Option<Named> {
v.as_scalar_value::<i32>().map(|i| Named(*i))
fn from_input_value(v: &InputValue) -> Result<Named, String> {
v.as_int_value()
.map(Named)
.ok_or_else(|| format!("Expected `Int`, found: {}", v))
}
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, DefaultScalarValue> {
@ -78,8 +82,10 @@ impl GraphQLScalar for ScalarDescription {
Value::scalar(self.0)
}
fn from_input_value(v: &InputValue) -> Option<ScalarDescription> {
v.as_scalar_value::<i32>().map(|i| ScalarDescription(*i))
fn from_input_value(v: &InputValue) -> Result<ScalarDescription, String> {
v.as_int_value()
.map(ScalarDescription)
.ok_or_else(|| format!("Expected `Int`, found: {}", v))
}
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, DefaultScalarValue> {
@ -98,10 +104,11 @@ macro_rules! impl_scalar {
Value::scalar(self.0.clone())
}
fn from_input_value(v: &InputValue) -> Option<Self> {
fn from_input_value(v: &InputValue) -> Result<Self, &'static str> {
v.as_scalar_value()
.and_then(|v| v.as_str())
.and_then(|s| Some(Self(s.to_owned())))
.ok_or_else(|| "Expected `String`")
}
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
@ -140,9 +147,10 @@ impl GraphQLScalar for WithCustomScalarValue {
Value::scalar(self.0)
}
fn from_input_value(v: &InputValue<MyScalarValue>) -> Option<WithCustomScalarValue> {
v.as_scalar_value::<i32>()
.map(|i| WithCustomScalarValue(*i))
fn from_input_value(v: &InputValue<MyScalarValue>) -> Result<WithCustomScalarValue, String> {
v.as_int_value()
.map(WithCustomScalarValue)
.ok_or_else(|| format!("Expected Int, found: {}", v))
}
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, MyScalarValue> {
@ -198,8 +206,10 @@ fn path_in_resolve_return_type() {
Value::scalar(self.0)
}
fn from_input_value(v: &InputValue) -> Option<ResolvePath> {
v.as_scalar_value::<i32>().map(|i| ResolvePath(*i))
fn from_input_value(v: &InputValue) -> Result<ResolvePath, String> {
v.as_int_value()
.map(ResolvePath)
.ok_or_else(|| format!("Expected `Int`, found: {}", v))
}
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, DefaultScalarValue> {

View file

@ -138,11 +138,10 @@ impl GraphQLScalar for i64 {
Value::scalar(*self)
}
fn from_input_value(v: &InputValue) -> Option<i64> {
match *v {
InputValue::Scalar(MyScalarValue::Long(i)) => Some(i),
_ => None,
}
fn from_input_value(v: &InputValue) -> Result<i64, String> {
v.as_scalar_value::<i64>()
.copied()
.ok_or_else(|| format!("Expected `MyScalarValue::Long`, found: {}", v))
}
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, MyScalarValue> {

View file

@ -7,6 +7,8 @@
- `#[graphql_object]` and `#[graphql_subscription]` macros expansion now preserves defined `impl` blocks "as is" and reuses defined methods in opaque way. ([#971](https://github.com/graphql-rust/juniper/pull/971))
- `rename = "<policy>"` attribute's argument renamed to `rename_all = "<policy>"`. ([#971](https://github.com/graphql-rust/juniper/pull/971))
- Upgrade `bson` feature to [2.0 version of its crate](https://github.com/mongodb/bson-rust/releases/tag/v2.0.0). ([#979](https://github.com/graphql-rust/juniper/pull/979))
- Make `FromInputValue` methods fallible to allow post-validation. ([#987](https://github.com/graphql-rust/juniper/pull/987))
- Change `Option` to `Result` in `from_input_value()` return type of `#[graphql_scalar]` macro. ([#987](https://github.com/graphql-rust/juniper/pull/987))
- Forbid `__typename` field on `subscription` operations [accordingly to October 2021 spec](https://spec.graphql.org/October2021/#note-bc213). ([#1001](https://github.com/graphql-rust/juniper/pull/1001), [#1000](https://github.com/graphql-rust/juniper/pull/1000))
## Features

View file

@ -150,22 +150,39 @@ pub type Document<'a, S> = [Definition<'a, S>];
#[doc(hidden)]
pub type OwnedDocument<'a, S> = Vec<Definition<'a, S>>;
/// Parse an unstructured input value into a Rust data type.
/// Parsing of an unstructured input value into a Rust data type.
///
/// The conversion _can_ fail, and must in that case return None. Implemented
/// automatically by the convenience proc macro `graphql_scalar` or by deriving GraphQLEnum.
/// The conversion _can_ fail, and must in that case return [`Err`]. Thus not
/// restricted in the definition of this trait, the returned [`Err`] should be
/// convertible with [`IntoFieldError`] to fit well into the library machinery.
///
/// Implemented automatically by the convenience proc macro [`graphql_scalar!`]
/// or by deriving `GraphQLEnum`.
///
/// Must be implemented manually when manually exposing new enums or scalars.
///
/// [`graphql_scalar!`]: macro@crate::graphql_scalar
/// [`IntoFieldError`]: crate::IntoFieldError
pub trait FromInputValue<S = DefaultScalarValue>: Sized {
/// Performs the conversion.
fn from_input_value(v: &InputValue<S>) -> Option<Self>;
/// Performs the conversion from an absent value (e.g. to distinguish between
/// implicit and explicit null). The default implementation just uses
/// `from_input_value` as if an explicit null were provided.
/// Type of this conversion error.
///
/// This conversion must not fail.
fn from_implicit_null() -> Option<Self> {
/// Thus not restricted, it should be convertible with [`IntoFieldError`] to
/// fit well into the library machinery.
///
/// [`IntoFieldError`]: crate::IntoFieldError
type Error;
/// Performs the conversion.
fn from_input_value(v: &InputValue<S>) -> Result<Self, Self::Error>;
/// Performs the conversion from an absent value (e.g. to distinguish
/// between implicit and explicit `null`).
///
/// The default implementation just calls [`from_input_value()`] as if an
/// explicit `null` was provided.
///
/// [`from_input_value()`]: FromInputValue::from_input_value
fn from_implicit_null() -> Result<Self, Self::Error> {
Self::from_input_value(&InputValue::<S>::Null)
}
}
@ -299,11 +316,8 @@ impl<S> InputValue<S> {
}
/// Shorthand form of invoking [`FromInputValue::from_input_value()`].
pub fn convert<T>(&self) -> Option<T>
where
T: FromInputValue<S>,
{
<T as FromInputValue<S>>::from_input_value(self)
pub fn convert<T: FromInputValue<S>>(&self) -> Result<T, T::Error> {
T::from_input_value(self)
}
/// Does the value represent a `null`?

View file

@ -149,47 +149,37 @@ where
/// Ok(s)
/// }
/// ```
#[derive(Debug, PartialEq)]
#[derive(Clone, Debug, PartialEq)]
pub struct FieldError<S = DefaultScalarValue> {
message: String,
extensions: Value<S>,
}
impl<T: Display, S> From<T> for FieldError<S>
where
S: crate::value::ScalarValue,
{
fn from(e: T) -> FieldError<S> {
FieldError {
message: format!("{}", e),
extensions: Value::null(),
impl<T: Display, S> From<T> for FieldError<S> {
fn from(e: T) -> Self {
Self {
message: e.to_string(),
extensions: Value::Null,
}
}
}
impl<S> FieldError<S> {
/// Construct a new error with additional data
///
/// You can use the `graphql_value!` macro to construct an error:
/// Construct a new [`FieldError`] with additional data.
///
/// You can use the [`graphql_value!`] macro for construction:
/// ```rust
/// use juniper::FieldError;
/// # use juniper::DefaultScalarValue;
/// use juniper::graphql_value;
/// use juniper::{graphql_value, FieldError};
///
/// # fn sample() {
/// # let _: FieldError<DefaultScalarValue> =
/// # let _: FieldError =
/// FieldError::new(
/// "Could not open connection to the database",
/// graphql_value!({ "internal_error": "Connection refused" })
/// graphql_value!({"internal_error": "Connection refused"}),
/// );
/// # }
/// # fn main() { }
/// ```
///
/// The `extensions` parameter will be added to the `"extensions"` field of the error
/// object in the JSON response:
///
/// The `extensions` parameter will be added to the `"extensions"` field of
/// the `"errors"` object in response:
/// ```json
/// {
/// "errors": [
@ -202,25 +192,34 @@ impl<S> FieldError<S> {
/// }
/// ```
///
/// If the argument is `Value::null()`, no extra data will be included.
pub fn new<T: Display>(e: T, extensions: Value<S>) -> FieldError<S> {
FieldError {
message: format!("{}", e),
/// If the argument is [`Value::Null`], then no extra data will be included.
///
/// [`graphql_value!`]: macro@crate::graphql_value
#[must_use]
pub fn new<T: Display>(e: T, extensions: Value<S>) -> Self {
Self {
message: e.to_string(),
extensions,
}
}
#[doc(hidden)]
/// Returns `"message"` field of this [`FieldError`].
#[must_use]
pub fn message(&self) -> &str {
&self.message
}
#[doc(hidden)]
/// Returns `"extensions"` field of this [`FieldError`].
///
/// If there is no `"extensions"`, then [`Value::Null`] will be returned.
#[must_use]
pub fn extensions(&self) -> &Value<S> {
&self.extensions
}
/// Maps the [`ScalarValue`] type of this [`FieldError`] into the specified one.
/// Maps the [`ScalarValue`] type of this [`FieldError`] into the specified
/// one.
#[must_use]
pub fn map_scalar_value<Into>(self) -> FieldError<Into>
where
S: ScalarValue,
@ -231,6 +230,15 @@ impl<S> FieldError<S> {
extensions: self.extensions.map_scalar_value(),
}
}
/// Maps the [`FieldError::message`] with the given function.
#[must_use]
pub fn map_message(self, f: impl FnOnce(String) -> String) -> Self {
Self {
message: f(self.message),
extensions: self.extensions,
}
}
}
/// The result of resolving the value of a field of type `T`
@ -246,17 +254,18 @@ pub type ValuesStream<'a, S = DefaultScalarValue> =
/// The map of variables used for substitution during query execution
pub type Variables<S = DefaultScalarValue> = HashMap<String, InputValue<S>>;
/// Custom error handling trait to enable Error types other than `FieldError` to be specified
/// as return value.
/// Custom error handling trait to enable error types other than [`FieldError`]
/// to be specified as return value.
///
/// Any custom error type should implement this trait to convert it to `FieldError`.
/// Any custom error type should implement this trait to convert itself into a
/// [`FieldError`].
pub trait IntoFieldError<S = DefaultScalarValue> {
#[doc(hidden)]
/// Performs the custom conversion into a [`FieldError`].
#[must_use]
fn into_field_error(self) -> FieldError<S>;
}
impl<S1: ScalarValue, S2: ScalarValue> IntoFieldError<S2> for FieldError<S1> {
#[inline]
fn into_field_error(self) -> FieldError<S2> {
self.map_scalar_value()
}
@ -268,6 +277,24 @@ impl<S> IntoFieldError<S> for std::convert::Infallible {
}
}
impl<'a, S> IntoFieldError<S> for &'a str {
fn into_field_error(self) -> FieldError<S> {
FieldError::<S>::from(self)
}
}
impl<S> IntoFieldError<S> for String {
fn into_field_error(self) -> FieldError<S> {
FieldError::<S>::from(self)
}
}
impl<'a, S> IntoFieldError<S> for Cow<'a, str> {
fn into_field_error(self) -> FieldError<S> {
FieldError::<S>::from(self)
}
}
#[doc(hidden)]
pub trait IntoResolvable<'a, S, T, C>
where
@ -1125,22 +1152,21 @@ where
Ok((value, errors))
}
impl<'r, S> Registry<'r, S>
where
S: ScalarValue + 'r,
{
/// Construct a new registry
pub fn new(types: FnvHashMap<Name, MetaType<'r, S>>) -> Registry<'r, S> {
Registry { types }
impl<'r, S: 'r> Registry<'r, S> {
/// Constructs a new [`Registry`] out of the given `types`.
pub fn new(types: FnvHashMap<Name, MetaType<'r, S>>) -> Self {
Self { types }
}
/// Get the `Type` instance for a given GraphQL type
/// Returns a [`Type`] instance for the given [`GraphQLType`], registered in
/// this [`Registry`].
///
/// If the registry hasn't seen a type with this name before, it will
/// construct its metadata and store it.
/// If this [`Registry`] hasn't seen a [`Type`] with such
/// [`GraphQLType::name`] before, it will construct the one and store it.
pub fn get_type<T>(&mut self, info: &T::TypeInfo) -> Type<'r>
where
T: GraphQLType<S> + ?Sized,
S: ScalarValue,
{
if let Some(name) = T::name(info) {
let validated_name = name.parse::<Name>().unwrap();
@ -1158,10 +1184,11 @@ where
}
}
/// Create a field with the provided name
/// Creates a [`Field`] with the provided `name`.
pub fn field<T>(&mut self, name: &str, info: &T::TypeInfo) -> Field<'r, S>
where
T: GraphQLType<S> + ?Sized,
S: ScalarValue,
{
Field {
name: smartstring::SmartString::from(name),
@ -1180,6 +1207,7 @@ where
) -> Field<'r, S>
where
I: GraphQLType<S>,
S: ScalarValue,
{
Field {
name: smartstring::SmartString::from(name),
@ -1190,18 +1218,19 @@ where
}
}
/// Create an argument with the provided name
/// Creates an [`Argument`] with the provided `name`.
pub fn arg<T>(&mut self, name: &str, info: &T::TypeInfo) -> Argument<'r, S>
where
T: GraphQLType<S> + FromInputValue<S> + ?Sized,
T: GraphQLType<S> + FromInputValue<S>,
S: ScalarValue,
{
Argument::new(name, self.get_type::<T>(info))
}
/// Create an argument with a 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>`.
/// When called with type `T`, the actual [`Argument`] will be given the
/// type `Option<T>`.
pub fn arg_with_default<T>(
&mut self,
name: &str,
@ -1209,7 +1238,8 @@ where
info: &T::TypeInfo,
) -> Argument<'r, S>
where
T: GraphQLType<S> + ToInputValue<S> + FromInputValue<S> + ?Sized,
T: GraphQLType<S> + ToInputValue<S> + FromInputValue<S>,
S: ScalarValue,
{
Argument::new(name, self.get_type::<Option<T>>(info)).default_value(value.to_input_value())
}
@ -1220,40 +1250,46 @@ where
.or_insert(MetaType::Placeholder(PlaceholderMeta { of_type }));
}
/// Create a scalar meta type
///
/// This expects the type to implement `FromInputValue`.
/// Creates a [`ScalarMeta`] type.
pub fn build_scalar_type<T>(&mut self, info: &T::TypeInfo) -> ScalarMeta<'r, S>
where
T: FromInputValue<S> + GraphQLType<S> + ParseScalarValue<S> + ?Sized + 'r,
T: GraphQLType<S> + FromInputValue<S> + ParseScalarValue<S> + 'r,
T::Error: IntoFieldError<S>,
S: ScalarValue,
{
let name = T::name(info).expect("Scalar types must be named. Implement name()");
let name = T::name(info).expect("Scalar types must be named. Implement `name()`");
ScalarMeta::new::<T>(Cow::Owned(name.to_string()))
}
/// Create a list meta type
pub fn build_list_type<T: GraphQLType<S> + ?Sized>(
/// Creates a [`ListMeta`] type.
///
/// Specifying `expected_size` will be used to ensure that values of this
/// type will always match it.
pub fn build_list_type<T>(
&mut self,
info: &T::TypeInfo,
expected_size: Option<usize>,
) -> ListMeta<'r> {
) -> ListMeta<'r>
where
T: GraphQLType<S> + ?Sized,
S: ScalarValue,
{
let of_type = self.get_type::<T>(info);
ListMeta::new(of_type, expected_size)
}
/// Create a nullable meta type
pub fn build_nullable_type<T: GraphQLType<S> + ?Sized>(
&mut self,
info: &T::TypeInfo,
) -> NullableMeta<'r> {
/// Creates a [`NullableMeta`] type.
pub fn build_nullable_type<T>(&mut self, info: &T::TypeInfo) -> NullableMeta<'r>
where
T: GraphQLType<S> + ?Sized,
S: ScalarValue,
{
let of_type = self.get_type::<T>(info);
NullableMeta::new(of_type)
}
/// Create an object meta type
///
/// To prevent infinite recursion by enforcing ordering, this returns a
/// function that needs to be called with the list of fields on the object.
/// Creates an [`ObjectMeta`] type with the given `fields`.
pub fn build_object_type<T>(
&mut self,
info: &T::TypeInfo,
@ -1261,6 +1297,7 @@ where
) -> ObjectMeta<'r, S>
where
T: GraphQLType<S> + ?Sized,
S: ScalarValue,
{
let name = T::name(info).expect("Object types must be named. Implement name()");
@ -1269,22 +1306,23 @@ where
ObjectMeta::new(Cow::Owned(name.to_string()), &v)
}
/// Create an enum meta type
/// Creates an [`EnumMeta`] type out of the provided `values`.
pub fn build_enum_type<T>(
&mut self,
info: &T::TypeInfo,
values: &[EnumValue],
) -> EnumMeta<'r, S>
where
T: FromInputValue<S> + GraphQLType<S> + ?Sized,
T: GraphQLType<S> + FromInputValue<S>,
T::Error: IntoFieldError<S>,
S: ScalarValue,
{
let name = T::name(info).expect("Enum types must be named. Implement name()");
let name = T::name(info).expect("Enum types must be named. Implement `name()`");
EnumMeta::new::<T>(Cow::Owned(name.to_string()), values)
}
/// Create an interface meta type,
/// by providing a type info object.
/// Creates an [`InterfaceMeta`] type with the given `fields`.
pub fn build_interface_type<T>(
&mut self,
info: &T::TypeInfo,
@ -1292,6 +1330,7 @@ where
) -> InterfaceMeta<'r, S>
where
T: GraphQLType<S> + ?Sized,
S: ScalarValue,
{
let name = T::name(info).expect("Interface types must be named. Implement name()");
@ -1300,24 +1339,27 @@ where
InterfaceMeta::new(Cow::Owned(name.to_string()), &v)
}
/// Create a union meta type
/// Creates an [`UnionMeta`] type of the given `types`.
pub fn build_union_type<T>(&mut self, info: &T::TypeInfo, types: &[Type<'r>]) -> UnionMeta<'r>
where
T: GraphQLType<S> + ?Sized,
S: ScalarValue,
{
let name = T::name(info).expect("Union types must be named. Implement name()");
UnionMeta::new(Cow::Owned(name.to_string()), types)
}
/// Create an input object meta type
/// Creates an [`InputObjectMeta`] type with the given `args`.
pub fn build_input_object_type<T>(
&mut self,
info: &T::TypeInfo,
args: &[Argument<'r, S>],
) -> InputObjectMeta<'r, S>
where
T: FromInputValue<S> + GraphQLType<S> + ?Sized,
T: GraphQLType<S> + FromInputValue<S>,
T::Error: IntoFieldError<S>,
S: ScalarValue,
{
let name = T::name(info).expect("Input object types must be named. Implement name()");

View file

@ -204,9 +204,9 @@ fn default_name_input_value() {
"fieldTwo": "number two",
});
let dv: Option<DefaultName> = FromInputValue::from_input_value(&iv);
let dv = DefaultName::from_input_value(&iv);
assert!(dv.is_some());
assert!(dv.is_ok(), "error: {}", dv.unwrap_err().message());
let dv = dv.unwrap();

View file

@ -9,7 +9,7 @@ use crate::{
graphql_interface, graphql_object, graphql_scalar, graphql_value, graphql_vars,
schema::model::RootNode,
types::scalars::{EmptyMutation, EmptySubscription},
value::{ParseScalarResult, ParseScalarValue, ScalarValue, Value},
value::{ParseScalarResult, ParseScalarValue, Value},
GraphQLEnum,
};
@ -28,8 +28,10 @@ impl<S: ScalarValue> GraphQLScalar for Scalar {
Value::scalar(self.0)
}
fn from_input_value(v: &InputValue) -> Option<Scalar> {
v.as_scalar().and_then(ScalarValue::as_int).map(Scalar)
fn from_input_value(v: &InputValue) -> Result<Scalar, String> {
v.as_int_value()
.map(Scalar)
.ok_or_else(|| format!("Expected `Int`, found: {}", v))
}
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {

View file

@ -5,7 +5,7 @@ use crate::{
schema::model::RootNode,
types::scalars::{EmptyMutation, EmptySubscription},
validation::RuleError,
value::{DefaultScalarValue, Object, ParseScalarResult, ParseScalarValue, ScalarValue},
value::{DefaultScalarValue, Object, ParseScalarResult, ParseScalarValue},
GraphQLError::ValidationError,
GraphQLInputObject,
};
@ -19,14 +19,11 @@ impl<S: ScalarValue> GraphQLScalar for TestComplexScalar {
graphql_value!("SerializedValue")
}
fn from_input_value(v: &InputValue) -> Option<TestComplexScalar> {
if let Some(s) = v.as_scalar().and_then(ScalarValue::as_str) {
if s == "SerializedValue" {
return Some(TestComplexScalar);
}
}
None
fn from_input_value(v: &InputValue) -> Result<TestComplexScalar, String> {
v.as_string_value()
.filter(|s| *s == "SerializedValue")
.map(|_| TestComplexScalar)
.ok_or_else(|| format!(r#"Expected "SerializedValue" string, found: {}"#, v))
}
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
@ -1075,7 +1072,8 @@ mod integers {
assert_eq!(
error,
ValidationError(vec![RuleError::new(
r#"Variable "$var" got invalid value. Expected "Int"."#,
"Variable \"$var\" got invalid value. Expected input scalar `Int`. \
Got: `10`. Details: Expected `Int`, found: 10.",
&[SourcePosition::new(8, 0, 8)],
)]),
);
@ -1099,7 +1097,9 @@ mod integers {
assert_eq!(
error,
ValidationError(vec![RuleError::new(
r#"Variable "$var" got invalid value. Expected "Int"."#,
"Variable \"$var\" got invalid value. \
Expected input scalar `Int`. Got: `\"10\"`. \
Details: Expected `Int`, found: \"10\".",
&[SourcePosition::new(8, 0, 8)],
)]),
);
@ -1157,7 +1157,9 @@ mod floats {
assert_eq!(
error,
ValidationError(vec![RuleError::new(
r#"Variable "$var" got invalid value. Expected "Float"."#,
"Variable \"$var\" got invalid value. \
Expected input scalar `Float`. Got: `\"10\"`. \
Details: Expected `Float`, found: \"10\".",
&[SourcePosition::new(8, 0, 8)],
)]),
);

View file

@ -19,8 +19,12 @@ where
Value::scalar(self.to_hex())
}
fn from_input_value(v: &InputValue) -> Option<ObjectId> {
v.as_string_value().and_then(|s| Self::parse_str(s).ok())
fn from_input_value(v: &InputValue) -> Result<ObjectId, String> {
v.as_string_value()
.ok_or_else(|| format!("Expected `String`, found: {}", v))
.and_then(|s| {
Self::parse_str(s).map_err(|e| format!("Failed to parse `ObjectId`: {}", e))
})
}
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
@ -41,9 +45,13 @@ where
Value::scalar((*self).to_chrono().to_rfc3339())
}
fn from_input_value(v: &InputValue) -> Option<UtcDateTime> {
fn from_input_value(v: &InputValue) -> Result<UtcDateTime, String> {
v.as_string_value()
.and_then(|s| (s.parse::<DateTime<Utc>>().ok()))
.ok_or_else(|| format!("Expected `String`, found: {}", v))
.and_then(|s| {
s.parse::<DateTime<Utc>>()
.map_err(|e| format!("Failed to parse `UtcDateTime`: {}", e))
})
.map(Self::from_chrono)
}

View file

@ -36,9 +36,13 @@ where
Value::scalar(self.to_rfc3339())
}
fn from_input_value(v: &InputValue) -> Option<DateTime<FixedOffset>> {
fn from_input_value(v: &InputValue) -> Result<DateTime<FixedOffset>, String> {
v.as_string_value()
.and_then(|s| DateTime::parse_from_rfc3339(s).ok())
.ok_or_else(|| format!("Expected `String`, found: {}", v))
.and_then(|s| {
DateTime::parse_from_rfc3339(s)
.map_err(|e| format!("Failed to parse `DateTimeFixedOffset`: {}", e))
})
}
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
@ -59,9 +63,13 @@ where
Value::scalar(self.to_rfc3339())
}
fn from_input_value(v: &InputValue) -> Option<DateTime<Utc>> {
fn from_input_value(v: &InputValue) -> Result<DateTime<Utc>, String> {
v.as_string_value()
.and_then(|s| (s.parse::<DateTime<Utc>>().ok()))
.ok_or_else(|| format!("Expected `String`, found: {}", v))
.and_then(|s| {
s.parse::<DateTime<Utc>>()
.map_err(|e| format!("Failed to parse `DateTimeUtc`: {}", e))
})
}
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
@ -87,9 +95,13 @@ where
Value::scalar(self.format("%Y-%m-%d").to_string())
}
fn from_input_value(v: &InputValue) -> Option<NaiveDate> {
fn from_input_value(v: &InputValue) -> Result<NaiveDate, String> {
v.as_string_value()
.and_then(|s| NaiveDate::parse_from_str(s, "%Y-%m-%d").ok())
.ok_or_else(|| format!("Expected `String`, found: {}", v))
.and_then(|s| {
NaiveDate::parse_from_str(s, "%Y-%m-%d")
.map_err(|e| format!("Failed to parse `NaiveDate`: {}", e))
})
}
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
@ -111,9 +123,13 @@ where
Value::scalar(self.format("%H:%M:%S").to_string())
}
fn from_input_value(v: &InputValue) -> Option<NaiveTime> {
fn from_input_value(v: &InputValue) -> Result<NaiveTime, String> {
v.as_string_value()
.and_then(|s| NaiveTime::parse_from_str(s, "%H:%M:%S").ok())
.ok_or_else(|| format!("Expected `String`, found: {}", v))
.and_then(|s| {
NaiveTime::parse_from_str(s, "%H:%M:%S")
.map_err(|e| format!("Failed to parse `NaiveTime`: {}", e))
})
}
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
@ -136,9 +152,14 @@ where
Value::scalar(self.timestamp() as f64)
}
fn from_input_value(v: &InputValue) -> Option<NaiveDateTime> {
fn from_input_value(v: &InputValue) -> Result<NaiveDateTime, String> {
v.as_float_value()
.and_then(|f| NaiveDateTime::from_timestamp_opt(f as i64, 0))
.ok_or_else(|| format!("Expected `Float`, found: {}", v))
.and_then(|f| {
let secs = f as i64;
NaiveDateTime::from_timestamp_opt(secs, 0)
.ok_or_else(|| format!("Out-of-range number of seconds: {}", secs))
})
}
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {

View file

@ -21,8 +21,13 @@ where
Value::scalar(self.name().to_owned())
}
fn from_input_value(v: &InputValue) -> Option<Tz> {
v.as_string_value().and_then(|s| s.parse::<Tz>().ok())
fn from_input_value(v: &InputValue) -> Result<Tz, String> {
v.as_string_value()
.ok_or_else(|| format!("Expected `String`, found: {}", v))
.and_then(|s| {
s.parse::<Tz>()
.map_err(|e| format!("Failed to parse `Tz`: {}", e))
})
}
fn from_str<'a>(val: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
@ -37,25 +42,30 @@ where
#[cfg(test)]
mod test {
mod from_input_value {
use std::ops::Deref;
use chrono_tz::Tz;
use crate::{graphql_input_value, FromInputValue, InputValue};
fn tz_input_test(raw: &'static str, expected: Option<Tz>) {
fn tz_input_test(raw: &'static str, expected: Result<Tz, &str>) {
let input: InputValue = graphql_input_value!((raw));
let parsed: Option<Tz> = FromInputValue::from_input_value(&input);
let parsed = FromInputValue::from_input_value(&input);
assert_eq!(parsed, expected);
assert_eq!(
parsed.as_ref().map_err(Deref::deref),
expected.as_ref().map_err(Deref::deref),
);
}
#[test]
fn europe_zone() {
tz_input_test("Europe/London", Some(chrono_tz::Europe::London));
tz_input_test("Europe/London", Ok(chrono_tz::Europe::London));
}
#[test]
fn etc_minus() {
tz_input_test("Etc/GMT-3", Some(chrono_tz::Etc::GMTMinus3));
tz_input_test("Etc/GMT-3", Ok(chrono_tz::Etc::GMTMinus3));
}
mod invalid {
@ -63,17 +73,26 @@ mod test {
#[test]
fn forward_slash() {
tz_input_test("Abc/Xyz", None);
tz_input_test(
"Abc/Xyz",
Err("Failed to parse `Tz`: received invalid timezone"),
);
}
#[test]
fn number() {
tz_input_test("8086", None);
tz_input_test(
"8086",
Err("Failed to parse `Tz`: received invalid timezone"),
);
}
#[test]
fn no_forward_slash() {
tz_input_test("AbcXyz", None);
tz_input_test(
"AbcXyz",
Err("Failed to parse `Tz`: received invalid timezone"),
);
}
}
}

View file

@ -16,8 +16,10 @@ where
Value::scalar(self.as_str().to_owned())
}
fn from_input_value(v: &InputValue) -> Option<Url> {
v.as_string_value().and_then(|s| Url::parse(s).ok())
fn from_input_value(v: &InputValue) -> Result<Url, String> {
v.as_string_value()
.ok_or_else(|| format!("Expected `String`, found: {}", v))
.and_then(|s| Url::parse(s).map_err(|e| format!("Failed to parse `Url`: {}", e)))
}
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {

View file

@ -19,8 +19,10 @@ where
Value::scalar(self.to_string())
}
fn from_input_value(v: &InputValue) -> Option<Uuid> {
v.as_string_value().and_then(|s| Uuid::parse_str(s).ok())
fn from_input_value(v: &InputValue) -> Result<Uuid, String> {
v.as_string_value()
.ok_or_else(|| format!("Expected `String`, found: {}", v))
.and_then(|s| Uuid::parse_str(s).map_err(|e| format!("Failed to parse `Uuid`: {}", e)))
}
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {

View file

@ -116,8 +116,9 @@ pub use juniper_codegen::{
GraphQLEnum, GraphQLInputObject, GraphQLObject, GraphQLScalarValue, GraphQLUnion,
};
#[doc(hidden)]
#[macro_use]
mod macros;
pub mod macros;
mod ast;
pub mod executor;
mod introspection;

View file

@ -2,7 +2,11 @@
pub mod subscription;
use crate::{DefaultScalarValue, DynGraphQLValue, DynGraphQLValueAsync, ScalarValue};
use std::fmt;
use futures::future::{self, BoxFuture};
use crate::{DefaultScalarValue, DynGraphQLValue, DynGraphQLValueAsync, FieldError, ScalarValue};
/// Conversion of a [`GraphQLValue`] to its [trait object][1].
///
@ -32,3 +36,50 @@ pub trait AsDynGraphQLValue<S: ScalarValue = DefaultScalarValue> {
}
crate::sa::assert_obj_safe!(AsDynGraphQLValue<Context = (), TypeInfo = ()>);
/// This trait is used by [`graphql_scalar!`] macro to retrieve [`Error`] type
/// from a [`Result`].
///
/// [`Error`]: Result::Error
/// [`graphql_scalar!`]: macro@crate::graphql_scalar
pub trait ExtractError {
/// Extracted [`Error`] type of this [`Result`].
///
/// [`Error`]: Result::Error
type Error;
}
impl<T, E> ExtractError for Result<T, E> {
type Error = E;
}
/// Wraps `msg` with [`Display`] implementation into opaque [`Send`] [`Future`]
/// which immediately resolves into [`FieldError`].
pub fn err_fut<'ok, D, Ok, S>(msg: D) -> BoxFuture<'ok, Result<Ok, FieldError<S>>>
where
D: fmt::Display,
Ok: Send + 'ok,
S: Send + 'static,
{
Box::pin(future::err(FieldError::from(msg)))
}
/// Generates a [`FieldError`] for the given Rust type expecting to have
/// [`GraphQLType::name`].
///
/// [`GraphQLType::name`]: crate::GraphQLType::name
pub fn err_unnamed_type<S>(name: &str) -> FieldError<S> {
FieldError::from(format!(
"Expected `{}` type to implement `GraphQLType::name`",
name,
))
}
/// Returns a [`future::err`] wrapping the [`err_unnamed_type`].
pub fn err_unnamed_type_fut<'ok, Ok, S>(name: &str) -> BoxFuture<'ok, Result<Ok, FieldError<S>>>
where
Ok: Send + 'ok,
S: Send + 'static,
{
Box::pin(future::err(err_unnamed_type(name)))
}

View file

@ -1,5 +1,6 @@
//! Declarative macros and helper definitions for procedural macros.
#[doc(hidden)]
pub mod helper;
#[macro_use]

View file

@ -8,7 +8,7 @@ use crate::{
},
types::scalars::{EmptyMutation, EmptySubscription},
value::{DefaultScalarValue, ParseScalarValue, ScalarValue},
GraphQLEnum, GraphQLInputObject,
GraphQLEnum, GraphQLInputObject, IntoFieldError,
};
#[derive(GraphQLEnum)]
@ -52,9 +52,10 @@ impl Query {
}
}
fn scalar_meta<T>(name: &'static str) -> MetaType<DefaultScalarValue>
fn scalar_meta<T>(name: &'static str) -> MetaType
where
T: FromInputValue<DefaultScalarValue> + ParseScalarValue<DefaultScalarValue> + 'static,
T: FromInputValue<DefaultScalarValue> + ParseScalarValue<DefaultScalarValue>,
T::Error: IntoFieldError,
{
MetaType::Scalar(ScalarMeta::new::<T>(name.into()))
}

View file

@ -1,5 +1,6 @@
//! Types used to describe a `GraphQL` schema
use juniper::IntoFieldError;
use std::{
borrow::{Cow, ToOwned},
fmt,
@ -10,7 +11,8 @@ use crate::{
parser::{ParseError, ScalarToken},
schema::model::SchemaType,
types::base::TypeKind,
value::{DefaultScalarValue, ParseScalarValue, ScalarValue},
value::{DefaultScalarValue, ParseScalarValue},
FieldError,
};
/// Whether an item is deprecated, with context.
@ -46,7 +48,7 @@ pub struct ScalarMeta<'a, S> {
pub name: Cow<'a, str>,
#[doc(hidden)]
pub description: Option<String>,
pub(crate) try_parse_fn: for<'b> fn(&'b InputValue<S>) -> bool,
pub(crate) try_parse_fn: for<'b> fn(&'b InputValue<S>) -> Result<(), FieldError<S>>,
pub(crate) parse_fn: for<'b> fn(ScalarToken<'b>) -> Result<S, ParseError<'b>>,
}
@ -88,7 +90,7 @@ pub struct EnumMeta<'a, S> {
pub description: Option<String>,
#[doc(hidden)]
pub values: Vec<EnumValue>,
pub(crate) try_parse_fn: for<'b> fn(&'b InputValue<S>) -> bool,
pub(crate) try_parse_fn: for<'b> fn(&'b InputValue<S>) -> Result<(), FieldError<S>>,
}
/// Interface type metadata
@ -121,7 +123,7 @@ pub struct InputObjectMeta<'a, S> {
pub description: Option<String>,
#[doc(hidden)]
pub input_fields: Vec<Argument<'a, S>>,
pub(crate) try_parse_fn: for<'b> fn(&'b InputValue<S>) -> bool,
pub(crate) try_parse_fn: for<'b> fn(&'b InputValue<S>) -> Result<(), FieldError<S>>,
}
/// A placeholder for not-yet-registered types
@ -323,7 +325,9 @@ impl<'a, S> MetaType<'a, S> {
/// `true` if it can be parsed as the provided type.
///
/// Only scalars, enums, and input objects have parse functions.
pub fn input_value_parse_fn(&self) -> Option<for<'b> fn(&'b InputValue<S>) -> bool> {
pub fn input_value_parse_fn(
&self,
) -> Option<for<'b> fn(&'b InputValue<S>) -> Result<(), FieldError<S>>> {
match *self {
MetaType::Scalar(ScalarMeta {
ref try_parse_fn, ..
@ -407,16 +411,14 @@ impl<'a, S> MetaType<'a, S> {
}
}
impl<'a, S> ScalarMeta<'a, S>
where
S: ScalarValue + 'a,
{
/// Build a new scalar type metadata with the specified name
impl<'a, S> ScalarMeta<'a, S> {
/// Builds a new [`ScalarMeta`] type with the specified `name`.
pub fn new<T>(name: Cow<'a, str>) -> Self
where
T: FromInputValue<S> + ParseScalarValue<S> + 'a,
T: FromInputValue<S> + ParseScalarValue<S>,
T::Error: IntoFieldError<S>,
{
ScalarMeta {
Self {
name,
description: None,
try_parse_fn: try_parse_fn::<S, T>,
@ -424,22 +426,25 @@ where
}
}
/// Set the description for the given scalar type
/// Sets the `description` of this [`ScalarMeta`] type.
///
/// If a description already was set prior to calling this method, it will be overwritten.
pub fn description(mut self, description: &str) -> ScalarMeta<'a, S> {
/// Overwrites any previously set description.
pub fn description(mut self, description: &str) -> Self {
self.description = Some(description.to_owned());
self
}
/// Wrap the scalar in a generic meta type
/// Wraps this [`ScalarMeta`] type into a generic [`MetaType`].
pub fn into_meta(self) -> MetaType<'a, S> {
MetaType::Scalar(self)
}
}
impl<'a> ListMeta<'a> {
/// Build a new list type by wrapping the specified type
/// Build a new [`ListMeta`] type by wrapping the specified [`Type`].
///
/// Specifying `expected_size` will be used to ensure that values of this
/// type will always match it.
pub fn new(of_type: Type<'a>, expected_size: Option<usize>) -> Self {
Self {
of_type,
@ -447,31 +452,31 @@ impl<'a> ListMeta<'a> {
}
}
/// Wrap the list in a generic meta type
/// Wraps this [`ListMeta`] type into a generic [`MetaType`].
pub fn into_meta<S>(self) -> MetaType<'a, S> {
MetaType::List(self)
}
}
impl<'a> NullableMeta<'a> {
/// Build a new nullable type by wrapping the specified type
pub fn new(of_type: Type<'a>) -> NullableMeta<'a> {
NullableMeta { of_type }
/// Build a new [`NullableMeta`] type by wrapping the specified [`Type`].
pub fn new(of_type: Type<'a>) -> Self {
Self { of_type }
}
/// Wrap the nullable type in a generic meta type
/// Wraps this [`NullableMeta`] type into a generic [`MetaType`].
pub fn into_meta<S>(self) -> MetaType<'a, S> {
MetaType::Nullable(self)
}
}
impl<'a, S> ObjectMeta<'a, S>
where
S: ScalarValue,
{
/// Build a new object type with the specified name and fields
pub fn new(name: Cow<'a, str>, fields: &[Field<'a, S>]) -> Self {
ObjectMeta {
impl<'a, S> ObjectMeta<'a, S> {
/// Build a new [`ObjectMeta`] type with the specified `name` and `fields`.
pub fn new(name: Cow<'a, str>, fields: &[Field<'a, S>]) -> Self
where
S: Clone,
{
Self {
name,
description: None,
fields: fields.to_vec(),
@ -479,19 +484,18 @@ where
}
}
/// Set the description for the object
/// Sets the `description` of this [`ObjectMeta`] type.
///
/// If a description was provided prior to calling this method, it will be overwritten.
pub fn description(mut self, description: &str) -> ObjectMeta<'a, S> {
/// Overwrites any previously set description.
pub fn description(mut self, description: &str) -> Self {
self.description = Some(description.to_owned());
self
}
/// Set the interfaces this type implements
/// Set the `interfaces` this [`ObjectMeta`] type implements.
///
/// If a list of interfaces already was provided prior to calling this method, they will be
/// overwritten.
pub fn interfaces(mut self, interfaces: &[Type<'a>]) -> ObjectMeta<'a, S> {
/// Overwrites any previously set list of interfaces.
pub fn interfaces(mut self, interfaces: &[Type<'a>]) -> Self {
self.interface_names = interfaces
.iter()
.map(|t| t.innermost_name().to_owned())
@ -499,74 +503,75 @@ where
self
}
/// Wrap this object type in a generic meta type
/// Wraps this [`ObjectMeta`] type into a generic [`MetaType`].
pub fn into_meta(self) -> MetaType<'a, S> {
MetaType::Object(self)
}
}
impl<'a, S> EnumMeta<'a, S>
where
S: ScalarValue + 'a,
{
/// Build a new enum type with the specified name and possible values
impl<'a, S> EnumMeta<'a, S> {
/// Build a new [`EnumMeta`] type with the specified `name` and possible
/// `values`.
pub fn new<T>(name: Cow<'a, str>, values: &[EnumValue]) -> Self
where
T: FromInputValue<S>,
T::Error: IntoFieldError<S>,
{
EnumMeta {
Self {
name,
description: None,
values: values.to_vec(),
values: values.to_owned(),
try_parse_fn: try_parse_fn::<S, T>,
}
}
/// Set the description of the type
/// Sets the `description` of this [`EnumMeta`] type.
///
/// If a description was provided prior to calling this method, it will be overwritten
pub fn description(mut self, description: &str) -> EnumMeta<'a, S> {
/// Overwrites any previously set description.
pub fn description(mut self, description: &str) -> Self {
self.description = Some(description.to_owned());
self
}
/// Wrap this enum type in a generic meta type
/// Wraps this [`EnumMeta`] type into a generic [`MetaType`].
pub fn into_meta(self) -> MetaType<'a, S> {
MetaType::Enum(self)
}
}
impl<'a, S> InterfaceMeta<'a, S>
where
S: ScalarValue,
{
/// Build a new interface type with the specified name and fields
pub fn new(name: Cow<'a, str>, fields: &[Field<'a, S>]) -> InterfaceMeta<'a, S> {
InterfaceMeta {
impl<'a, S> InterfaceMeta<'a, S> {
/// Builds a new [`InterfaceMeta`] type with the specified `name` and
/// `fields`.
pub fn new(name: Cow<'a, str>, fields: &[Field<'a, S>]) -> Self
where
S: Clone,
{
Self {
name,
description: None,
fields: fields.to_vec(),
}
}
/// Set the description of the type
/// Sets the `description` of this [`InterfaceMeta`] type.
///
/// If a description was provided prior to calling this method, it will be overwritten.
pub fn description(mut self, description: &str) -> InterfaceMeta<'a, S> {
/// Overwrites any previously set description.
pub fn description(mut self, description: &str) -> Self {
self.description = Some(description.to_owned());
self
}
/// Wrap this interface type in a generic meta type
/// Wraps this [`InterfaceMeta`] type into a generic [`MetaType`].
pub fn into_meta(self) -> MetaType<'a, S> {
MetaType::Interface(self)
}
}
impl<'a> UnionMeta<'a> {
/// Build a new union type with the specified name and possible types
pub fn new(name: Cow<'a, str>, of_types: &[Type]) -> UnionMeta<'a> {
UnionMeta {
/// Build a new [`UnionMeta`] type with the specified `name` and possible
/// [`Type`]s.
pub fn new(name: Cow<'a, str>, of_types: &[Type]) -> Self {
Self {
name,
description: None,
of_type_names: of_types
@ -576,30 +581,30 @@ impl<'a> UnionMeta<'a> {
}
}
/// Set the description of the type
/// Sets the `description` of this [`UnionMeta`] type.
///
/// If a description was provided prior to calling this method, it will be overwritten.
pub fn description(mut self, description: &str) -> UnionMeta<'a> {
/// Overwrites any previously set description.
pub fn description(mut self, description: &str) -> Self {
self.description = Some(description.to_owned());
self
}
/// Wrap this union type in a generic meta type
/// Wraps this [`UnionMeta`] type into a generic [`MetaType`].
pub fn into_meta<S>(self) -> MetaType<'a, S> {
MetaType::Union(self)
}
}
impl<'a, S> InputObjectMeta<'a, S>
where
S: ScalarValue,
{
/// Build a new input type with the specified name and input fields
impl<'a, S> InputObjectMeta<'a, S> {
/// Builds a new [`InputObjectMeta`] type with the specified `name` and
/// `input_fields`.
pub fn new<T>(name: Cow<'a, str>, input_fields: &[Argument<'a, S>]) -> Self
where
T: FromInputValue<S> + ?Sized,
T: FromInputValue<S>,
T::Error: IntoFieldError<S>,
S: Clone,
{
InputObjectMeta {
Self {
name,
description: None,
input_fields: input_fields.to_vec(),
@ -607,30 +612,30 @@ where
}
}
/// Set the description of the type
/// Set the `description` of this [`InputObjectMeta`] type.
///
/// If a description was provided prior to calling this method, it will be overwritten.
pub fn description(mut self, description: &str) -> InputObjectMeta<'a, S> {
/// Overwrites any previously set description.
pub fn description(mut self, description: &str) -> Self {
self.description = Some(description.to_owned());
self
}
/// Wrap this union type in a generic meta type
/// Wraps this [`InputObjectMeta`] type into a generic [`MetaType`].
pub fn into_meta(self) -> MetaType<'a, S> {
MetaType::InputObject(self)
}
}
impl<'a, S> Field<'a, S> {
/// Set the description of the field
/// Set the `description` of this [`Field`].
///
/// This overwrites the description if any was previously set.
/// Overwrites any previously set description.
pub fn description(mut self, description: &str) -> Self {
self.description = Some(description.to_owned());
self
}
/// Add an argument to the field
/// Adds an `argument` to this [`Field`].
///
/// Arguments are unordered and can't contain duplicates by name.
pub fn argument(mut self, argument: Argument<'a, S>) -> Self {
@ -642,13 +647,12 @@ impl<'a, S> Field<'a, S> {
args.push(argument);
}
};
self
}
/// Set the field to be deprecated with an optional reason.
/// Sets this [`Field`] as deprecated with an optional `reason`.
///
/// This overwrites the deprecation reason if any was previously set.
/// Overwrites any previously set deprecation reason.
pub fn deprecated(mut self, reason: Option<&str>) -> Self {
self.deprecation_status = DeprecationStatus::Deprecated(reason.map(ToOwned::to_owned));
self
@ -656,7 +660,7 @@ impl<'a, S> Field<'a, S> {
}
impl<'a, S> Argument<'a, S> {
#[doc(hidden)]
/// Builds a new [`Argument`] of the given [`Type`] with the given `name`.
pub fn new(name: &str, arg_type: Type<'a>) -> Self {
Self {
name: name.to_owned(),
@ -666,44 +670,44 @@ impl<'a, S> Argument<'a, S> {
}
}
/// Set the description of the argument
/// Sets the `description` of this [`Argument`].
///
/// This overwrites the description if any was previously set.
/// Overwrites any previously set description.
pub fn description(mut self, description: &str) -> Self {
self.description = Some(description.to_owned());
self
}
/// Set the default value of the argument
/// Set the default value of this [`Argument`].
///
/// This overwrites the default value if any was previously set.
pub fn default_value(mut self, default_value: InputValue<S>) -> Self {
self.default_value = Some(default_value);
/// Overwrites any previously set default value.
pub fn default_value(mut self, val: InputValue<S>) -> Self {
self.default_value = Some(val);
self
}
}
impl EnumValue {
/// Construct a new enum value with the provided name
pub fn new(name: &str) -> EnumValue {
EnumValue {
/// Constructs a new [`EnumValue`] with the provided `name`.
pub fn new(name: &str) -> Self {
Self {
name: name.to_owned(),
description: None,
deprecation_status: DeprecationStatus::Current,
}
}
/// Set the description of the enum value
/// Sets the `description` of this [`EnumValue`].
///
/// This overwrites the description if any was previously set.
pub fn description(mut self, description: &str) -> EnumValue {
/// Overwrites any previously set description.
pub fn description(mut self, description: &str) -> Self {
self.description = Some(description.to_owned());
self
}
/// Set the enum value to be deprecated with an optional reason.
/// Sets this [`EnumValue`] as deprecated with an optional `reason`.
///
/// This overwrites the deprecation reason if any was previously set.
/// Overwrites any previously set deprecation reason.
pub fn deprecated(mut self, reason: Option<&str>) -> Self {
self.deprecation_status = DeprecationStatus::Deprecated(reason.map(ToOwned::to_owned));
self
@ -739,9 +743,12 @@ impl<'a, S: fmt::Debug> fmt::Debug for InputObjectMeta<'a, S> {
}
}
fn try_parse_fn<S, T>(v: &InputValue<S>) -> bool
fn try_parse_fn<S, T>(v: &InputValue<S>) -> Result<(), FieldError<S>>
where
T: FromInputValue<S>,
T::Error: IntoFieldError<S>,
{
<T as FromInputValue<S>>::from_input_value(v).is_some()
T::from_input_value(v)
.map(drop)
.map_err(T::Error::into_field_error)
}

View file

@ -68,7 +68,7 @@ where
.replaced_context(&self.schema)
.resolve(&(), &self.schema),
"__type" => {
let type_name: String = args.get("name").unwrap();
let type_name: String = args.get("name")?.unwrap();
executor
.replaced_context(&self.schema)
.resolve(&(), &self.schema.type_by_name(&type_name))

View file

@ -6,7 +6,7 @@ use crate::{
parser::Spanning,
schema::meta::{Argument, MetaType},
value::{DefaultScalarValue, Object, ScalarValue, Value},
GraphQLEnum,
FieldResult, GraphQLEnum, IntoFieldError,
};
/// GraphQL type kind
@ -73,47 +73,55 @@ pub struct Arguments<'a, S = DefaultScalarValue> {
args: Option<IndexMap<&'a str, InputValue<S>>>,
}
impl<'a, S> Arguments<'a, S>
where
S: ScalarValue,
{
impl<'a, S> Arguments<'a, S> {
#[doc(hidden)]
pub fn new(
mut args: Option<IndexMap<&'a str, InputValue<S>>>,
meta_args: &'a Option<Vec<Argument<S>>>,
) -> Self {
) -> Self
where
S: Clone,
{
if meta_args.is_some() && args.is_none() {
args = Some(IndexMap::new());
}
if let (&mut Some(ref mut args), &Some(ref meta_args)) = (&mut args, meta_args) {
if let (Some(args), Some(meta_args)) = (&mut args, meta_args) {
for arg in meta_args {
if !args.contains_key(arg.name.as_str()) || args[arg.name.as_str()].is_null() {
if let Some(ref default_value) = arg.default_value {
args.insert(arg.name.as_str(), default_value.clone());
let arg_name = arg.name.as_str();
if args.get(arg_name).map_or(true, InputValue::is_null) {
if let Some(val) = arg.default_value.as_ref() {
args.insert(arg_name, val.clone());
}
}
}
}
Arguments { args }
Self { args }
}
/// Get and convert an argument into the desired type.
/// Gets an argument by the given `name` and converts it into the desired
/// type.
///
/// If the argument is found, or a default argument has been provided,
/// the `InputValue` will be converted into the type `T`.
/// If the argument is found, or a default argument has been provided, the
/// given [`InputValue`] will be converted into the type `T`.
///
/// Returns `Some` if the argument is present _and_ type conversion
/// succeeds.
pub fn get<T>(&self, key: &str) -> Option<T>
/// Returns [`None`] if an argument with such `name` is not present.
///
/// # Errors
///
/// If the [`FromInputValue`] conversion fails.
pub fn get<T>(&self, name: &str) -> FieldResult<Option<T>, S>
where
T: FromInputValue<S>,
T::Error: IntoFieldError<S>,
{
self.args
.as_ref()
.and_then(|args| args.get(key))
.and_then(InputValue::convert)
.and_then(|args| args.get(name))
.map(InputValue::convert)
.transpose()
.map_err(IntoFieldError::into_field_error)
}
}

View file

@ -5,7 +5,7 @@ use std::{
use crate::{
ast::{FromInputValue, InputValue, Selection, ToInputValue},
executor::{ExecutionResult, Executor, Registry},
executor::{ExecutionResult, Executor, FieldError, IntoFieldError, Registry},
schema::meta::MetaType,
types::{
async_await::GraphQLValueAsync,
@ -80,28 +80,22 @@ where
}
}
impl<S, T> FromInputValue<S> for Option<T>
where
T: FromInputValue<S>,
S: ScalarValue,
{
fn from_input_value(v: &InputValue<S>) -> Option<Self> {
impl<S, T: FromInputValue<S>> FromInputValue<S> for Option<T> {
type Error = T::Error;
fn from_input_value(v: &InputValue<S>) -> Result<Self, Self::Error> {
match v {
&InputValue::Null => Some(None),
InputValue::Null => Ok(None),
v => v.convert().map(Some),
}
}
}
impl<S, T> ToInputValue<S> for Option<T>
where
T: ToInputValue<S>,
S: ScalarValue,
{
impl<S, T: ToInputValue<S>> ToInputValue<S> for Option<T> {
fn to_input_value(&self) -> InputValue<S> {
match *self {
Some(ref v) => v.to_input_value(),
None => InputValue::null(),
match self {
Some(v) => v.to_input_value(),
None => InputValue::Null,
}
}
}
@ -163,18 +157,17 @@ where
}
}
impl<T, S> FromInputValue<S> for Vec<T>
where
T: FromInputValue<S>,
S: ScalarValue,
{
fn from_input_value(v: &InputValue<S>) -> Option<Self> {
match *v {
InputValue::List(ref ls) => {
let v: Vec<_> = ls.iter().filter_map(|i| i.item.convert()).collect();
(v.len() == ls.len()).then(|| v)
}
ref other => other.convert().map(|e| vec![e]),
impl<S, T: FromInputValue<S>> FromInputValue<S> for Vec<T> {
type Error = T::Error;
fn from_input_value(v: &InputValue<S>) -> Result<Self, Self::Error> {
match v {
InputValue::List(l) => l.iter().map(|i| i.item.convert()).collect(),
// See "Input Coercion" on List types:
// https://spec.graphql.org/June2018/#sec-Type-System.List
// In reality is intercepted by `Option`.
InputValue::Null => Ok(Vec::new()),
other => other.convert().map(|e| vec![e]),
}
}
}
@ -318,7 +311,9 @@ where
T: FromInputValue<S>,
S: ScalarValue,
{
fn from_input_value(v: &InputValue<S>) -> Option<Self> {
type Error = FromInputValueArrayError<T, S>;
fn from_input_value(v: &InputValue<S>) -> Result<Self, Self::Error> {
struct PartiallyInitializedArray<T, const N: usize> {
arr: [MaybeUninit<T>; N],
init_len: usize,
@ -345,6 +340,22 @@ where
match *v {
InputValue::List(ref ls) => {
if ls.len() != N {
return Err(FromInputValueArrayError::WrongCount {
actual: ls.len(),
expected: N,
});
}
if N == 0 {
// TODO: Use `mem::transmute` instead of
// `mem::transmute_copy` below, once it's allowed
// for const generics:
// https://github.com/rust-lang/rust/issues/61956
// SAFETY: `mem::transmute_copy` is safe here, because we
// check `N` to be `0`. It's no-op, actually.
return Ok(unsafe { mem::transmute_copy::<[T; 0], Self>(&[]) });
}
// SAFETY: The reason we're using a wrapper struct implementing
// `Drop` here is to be panic safe:
// `T: FromInputValue<S>` implementation is not
@ -363,20 +374,17 @@ where
no_drop: false,
};
let mut items = ls.iter().filter_map(|i| i.item.convert());
let mut items = ls.iter().map(|i| i.item.convert());
for elem in &mut out.arr[..] {
if let Some(i) = items.next() {
if let Some(i) = items
.next()
.transpose()
.map_err(FromInputValueArrayError::Scalar)?
{
*elem = MaybeUninit::new(i);
out.init_len += 1;
} else {
// There is not enough `items` to fill the array.
return None;
}
}
if items.next().is_some() {
// There is too much `items` to fit into the array.
return None;
}
// Do not drop collected `items`, because we're going to return
// them.
@ -391,29 +399,48 @@ where
// we won't have a double-free when `T: Drop` here,
// because original array elements are `MaybeUninit`, so
// do nothing on `Drop`.
Some(unsafe { mem::transmute_copy::<_, Self>(&out.arr) })
Ok(unsafe { mem::transmute_copy::<_, Self>(&out.arr) })
}
// See "Input Coercion" on List types:
// https://spec.graphql.org/June2018/#sec-Type-System.List
// In reality is intercepted by `Option`.
InputValue::Null if N == 0 => {
// TODO: Use `mem::transmute` instead of
// `mem::transmute_copy` below, once it's allowed
// for const generics:
// https://github.com/rust-lang/rust/issues/61956
// SAFETY: `mem::transmute_copy` is safe here, because we check
// `N` to be `0`. It's no-op, actually.
Ok(unsafe { mem::transmute_copy::<[T; 0], Self>(&[]) })
}
ref other => {
other.convert().and_then(|e: T| {
// TODO: Use `mem::transmute` instead of
// `mem::transmute_copy` below, once it's allowed for
// const generics:
// https://github.com/rust-lang/rust/issues/61956
if N == 1 {
// SAFETY: `mem::transmute_copy` is safe here, because
// we check `N` to be `1`.
// Also, despite `mem::transmute_copy` copies
// the value, we won't have a double-free when
// `T: Drop` here, because original `e: T` value
// is wrapped into `mem::ManuallyDrop`, so does
// nothing on `Drop`.
Some(unsafe {
mem::transmute_copy::<_, Self>(&[mem::ManuallyDrop::new(e)])
})
} else {
None
}
})
other
.convert()
.map_err(FromInputValueArrayError::Scalar)
.and_then(|e: T| {
// TODO: Use `mem::transmute` instead of
// `mem::transmute_copy` below, once it's allowed
// for const generics:
// https://github.com/rust-lang/rust/issues/61956
if N == 1 {
// SAFETY: `mem::transmute_copy` is safe here,
// because we check `N` to be `1`.
// Also, despite `mem::transmute_copy`
// copies the value, we won't have a
// double-free when `T: Drop` here, because
// original `e: T` value is wrapped into
// `mem::ManuallyDrop`, so does nothing on
// `Drop`.
Ok(unsafe {
mem::transmute_copy::<_, Self>(&[mem::ManuallyDrop::new(e)])
})
} else {
Err(FromInputValueArrayError::WrongCount {
actual: 1,
expected: N,
})
}
})
}
}
}
@ -429,6 +456,44 @@ where
}
}
/// Error converting [`InputValue`] into exact-size [`array`](prim@array).
pub enum FromInputValueArrayError<T, S>
where
T: FromInputValue<S>,
S: ScalarValue,
{
/// Wrong count of elements.
WrongCount {
/// Actual count of elements.
actual: usize,
/// Expected count of elements.
expected: usize,
},
/// Underlying [`ScalarValue`] conversion error.
Scalar(T::Error),
}
impl<T, S> IntoFieldError<S> for FromInputValueArrayError<T, S>
where
T: FromInputValue<S>,
T::Error: IntoFieldError<S>,
S: ScalarValue,
{
fn into_field_error(self) -> FieldError<S> {
const ERROR_PREFIX: &str = "Failed to convert into exact-size array";
match self {
Self::WrongCount { actual, expected } => format!(
"{}: wrong elements count: {} instead of {}",
ERROR_PREFIX, actual, expected
)
.into(),
Self::Scalar(s) => s.into_field_error(),
}
}
}
fn resolve_into_list<'t, S, T, I>(
executor: &Executor<T::Context, S>,
info: &T::TypeInfo,
@ -492,3 +557,19 @@ where
Ok(Value::list(values))
}
#[cfg(test)]
mod coercion {
use crate::{graphql_input_value, FromInputValue as _, InputValue};
type V = InputValue;
// See "Input Coercion" examples on List types:
// https://spec.graphql.org/June2018/#sec-Type-System.List
#[test]
fn vec() {
let v: V = graphql_input_value!([1, 2, 3]);
assert_eq!(<Vec<i32>>::from_input_value(&v), Ok(vec![1, 2, 3]),);
// TODO: all examples
}
}

View file

@ -278,20 +278,18 @@ where
}
}
impl<S, T> FromInputValue<S> for Nullable<T>
where
T: FromInputValue<S>,
S: ScalarValue,
{
fn from_input_value(v: &InputValue<S>) -> Option<Nullable<T>> {
impl<S, T: FromInputValue<S>> FromInputValue<S> for Nullable<T> {
type Error = <T as FromInputValue<S>>::Error;
fn from_input_value(v: &InputValue<S>) -> Result<Self, Self::Error> {
match v {
&InputValue::Null => Some(Self::ExplicitNull),
&InputValue::Null => Ok(Self::ExplicitNull),
v => v.convert().map(Self::Some),
}
}
fn from_implicit_null() -> Option<Self> {
Some(Self::ImplicitNull)
fn from_implicit_null() -> Result<Self, Self::Error> {
Ok(Self::ImplicitNull)
}
}

View file

@ -93,7 +93,9 @@ where
S: ScalarValue,
T: FromInputValue<S>,
{
fn from_input_value(v: &InputValue<S>) -> Option<Box<T>> {
type Error = T::Error;
fn from_input_value(v: &InputValue<S>) -> Result<Box<T>, Self::Error> {
<T as FromInputValue<S>>::from_input_value(v).map(Box::new)
}
}
@ -285,7 +287,9 @@ where
S: ScalarValue,
T: FromInputValue<S>,
{
fn from_input_value(v: &InputValue<S>) -> Option<Arc<T>> {
type Error = T::Error;
fn from_input_value(v: &InputValue<S>) -> Result<Arc<T>, Self::Error> {
<T as FromInputValue<S>>::from_input_value(v).map(Arc::new)
}
}

View file

@ -59,14 +59,12 @@ where
Value::scalar(self.0.clone())
}
fn from_input_value(v: &InputValue) -> Option<ID> {
match *v {
InputValue::Scalar(ref s) => s
.as_string()
.or_else(|| s.as_int().map(|i| i.to_string()))
.map(ID),
_ => None,
}
fn from_input_value(v: &InputValue) -> Result<ID, String> {
v.as_string_value()
.map(str::to_owned)
.or_else(|| v.as_int_value().map(|i| i.to_string()))
.map(ID)
.ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v))
}
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
@ -86,11 +84,10 @@ where
Value::scalar(self.clone())
}
fn from_input_value(v: &InputValue) -> Option<String> {
match *v {
InputValue::Scalar(ref s) => s.as_string(),
_ => None,
}
fn from_input_value(v: &InputValue) -> Result<String, String> {
v.as_string_value()
.map(str::to_owned)
.ok_or_else(|| format!("Expected `String`, found: {}", v))
}
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
@ -275,11 +272,10 @@ where
Value::scalar(*self)
}
fn from_input_value(v: &InputValue) -> Option<bool> {
match *v {
InputValue::Scalar(ref b) => b.as_boolean(),
_ => None,
}
fn from_input_value(v: &InputValue) -> Result<bool, String> {
v.as_scalar_value()
.and_then(ScalarValue::as_boolean)
.ok_or_else(|| format!("Expected `Boolean`, found: {}", v))
}
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
@ -297,11 +293,9 @@ where
Value::scalar(*self)
}
fn from_input_value(v: &InputValue) -> Option<i32> {
match *v {
InputValue::Scalar(ref i) => i.as_int(),
_ => None,
}
fn from_input_value(v: &InputValue) -> Result<i32, String> {
v.as_int_value()
.ok_or_else(|| format!("Expected `Int`, found: {}", v))
}
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
@ -324,11 +318,9 @@ where
Value::scalar(*self)
}
fn from_input_value(v: &InputValue) -> Option<f64> {
match *v {
InputValue::Scalar(ref s) => s.as_float(),
_ => None,
}
fn from_input_value(v: &InputValue) -> Result<f64, String> {
v.as_float_value()
.ok_or_else(|| format!("Expected `Float`, found: {}", v))
}
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {

View file

@ -57,7 +57,7 @@ where
InputValue::Null | InputValue::Variable(_) => true,
ref v @ InputValue::Scalar(_) | ref v @ InputValue::Enum(_) => {
if let Some(parse_fn) = t.input_value_parse_fn() {
parse_fn(v)
parse_fn(v).is_ok()
} else {
false
}

View file

@ -163,14 +163,17 @@ where
if e.is_empty() {
// All the fields didn't have errors, see if there is an
// overall error when parsing the input value.
if !(iom.try_parse_fn)(value) {
if let Err(e) = (iom.try_parse_fn)(value) {
errors.push(unification_error(
var_name,
var_pos,
&path,
&format!(
r#"Expected input of type "{}". Got: "{}""#,
iom.name, value
"Expected input of type `{}`. Got: `{}`. \
Details: {}",
iom.name,
value,
e.message(),
),
));
}
@ -193,16 +196,21 @@ fn unify_scalar<'a, S>(
path: &Path<'a>,
) -> Vec<RuleError>
where
S: fmt::Debug,
S: ScalarValue,
{
let mut errors: Vec<RuleError> = vec![];
if !(meta.try_parse_fn)(value) {
if let Err(e) = (meta.try_parse_fn)(value) {
return vec![unification_error(
var_name,
var_pos,
path,
&format!(r#"Expected "{}""#, meta.name),
&format!(
"Expected input scalar `{}`. Got: `{}`. Details: {}",
meta.name,
value,
e.message(),
),
)];
}

View file

@ -208,12 +208,14 @@ impl<S> FromInputValue<S> for DogCommand
where
S: ScalarValue,
{
fn from_input_value<'a>(v: &InputValue<S>) -> Option<DogCommand> {
type Error = &'static str;
fn from_input_value<'a>(v: &InputValue<S>) -> Result<DogCommand, Self::Error> {
match v.as_enum_value() {
Some("SIT") => Some(DogCommand::Sit),
Some("HEEL") => Some(DogCommand::Heel),
Some("DOWN") => Some(DogCommand::Down),
_ => None,
Some("SIT") => Ok(DogCommand::Sit),
Some("HEEL") => Ok(DogCommand::Heel),
Some("DOWN") => Ok(DogCommand::Down),
_ => Err("Unknown DogCommand"),
}
}
}
@ -314,13 +316,15 @@ impl<S> FromInputValue<S> for FurColor
where
S: ScalarValue,
{
fn from_input_value<'a>(v: &InputValue<S>) -> Option<FurColor> {
type Error = &'static str;
fn from_input_value<'a>(v: &InputValue<S>) -> Result<FurColor, Self::Error> {
match v.as_enum_value() {
Some("BROWN") => Some(FurColor::Brown),
Some("BLACK") => Some(FurColor::Black),
Some("TAN") => Some(FurColor::Tan),
Some("SPOTTED") => Some(FurColor::Spotted),
_ => None,
Some("BROWN") => Ok(FurColor::Brown),
Some("BLACK") => Ok(FurColor::Black),
Some("TAN") => Ok(FurColor::Tan),
Some("SPOTTED") => Ok(FurColor::Spotted),
_ => Err("Unknown FurColor"),
}
}
}
@ -612,21 +616,37 @@ impl<S> FromInputValue<S> for ComplexInput
where
S: ScalarValue,
{
fn from_input_value<'a>(v: &InputValue<S>) -> Option<ComplexInput> {
let obj = match v.to_object_value() {
Some(o) => o,
None => return None,
};
type Error = String;
Some(ComplexInput {
required_field: match obj.get("requiredField").and_then(|v| v.convert()) {
Some(f) => f,
None => return None,
},
int_field: obj.get("intField").and_then(|v| v.convert()),
string_field: obj.get("stringField").and_then(|v| v.convert()),
boolean_field: obj.get("booleanField").and_then(|v| v.convert()),
string_list_field: obj.get("stringListField").and_then(|v| v.convert()),
fn from_input_value<'a>(v: &InputValue<S>) -> Result<ComplexInput, Self::Error> {
let obj = v.to_object_value().ok_or("Expected object")?;
Ok(ComplexInput {
required_field: obj
.get("requiredField")
.map(|v| v.convert())
.transpose()?
.ok_or("Expected requiredField")?,
int_field: obj
.get("intField")
.map(|v| v.convert())
.transpose()?
.ok_or("Expected intField")?,
string_field: obj
.get("stringField")
.map(|v| v.convert())
.transpose()?
.ok_or("Expected stringField")?,
boolean_field: obj
.get("booleanField")
.map(|v| v.convert())
.transpose()?
.ok_or("Expected booleanField")?,
string_list_field: obj
.get("stringListField")
.map(|v| v.convert())
.transpose()?
.ok_or("Expected stringListField")?,
})
}
}

View file

@ -22,7 +22,7 @@ pub use self::{
/// values or variables. Also, lists and objects do not contain any location
/// information since they are generated by resolving fields and values rather
/// than parsing a source query.
#[derive(Debug, PartialEq, Clone)]
#[derive(Clone, Debug, PartialEq)]
#[allow(missing_docs)]
pub enum Value<S = DefaultScalarValue> {
Null,

View file

@ -347,18 +347,34 @@ impl OnMethod {
///
/// [`GraphQLValue::resolve_field`]: juniper::GraphQLValue::resolve_field
#[must_use]
pub(crate) fn method_resolve_field_tokens(&self, scalar: &scalar::Type) -> TokenStream {
pub(crate) fn method_resolve_field_tokens(
&self,
scalar: &scalar::Type,
for_async: bool,
) -> TokenStream {
match self {
Self::Regular(arg) => {
let (name, ty) = (&arg.name, &arg.ty);
let err_text = format!(
"Internal error: missing argument `{}` - validation must have failed",
&name,
);
quote! {
args.get::<#ty>(#name)
.or_else(::juniper::FromInputValue::<#scalar>::from_implicit_null)
.expect(#err_text)
let err_text = format!("Missing argument `{}`: {{}}", &name);
let arg = quote! {
args.get::<#ty>(#name).and_then(|opt| opt.map_or_else(|| {
<#ty as ::juniper::FromInputValue<#scalar>>::from_implicit_null()
.map_err(|e| {
::juniper::IntoFieldError::<#scalar>::into_field_error(e)
.map_message(|m| format!(#err_text, m))
})
}, Ok))
};
if for_async {
quote! {
match #arg {
Ok(v) => v,
Err(e) => return Box::pin(async { Err(e) }),
}
}
} else {
quote! { #arg? }
}
}

View file

@ -268,39 +268,45 @@ impl Definition {
self.arguments.is_some()
}
/// Returns generated code that panics about unknown [GraphQL field][1]
/// Returns generated code that errors about unknown [GraphQL field][1]
/// tried to be resolved in the [`GraphQLValue::resolve_field`] method.
///
/// [`GraphQLValue::resolve_field`]: juniper::GraphQLValue::resolve_field
/// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields
#[must_use]
pub(crate) fn method_resolve_field_panic_no_field_tokens(scalar: &scalar::Type) -> TokenStream {
pub(crate) fn method_resolve_field_err_no_field_tokens(
scalar: &scalar::Type,
ty_name: &str,
) -> TokenStream {
quote! {
panic!(
return Err(::juniper::FieldError::from(format!(
"Field `{}` not found on type `{}`",
field,
<Self as ::juniper::GraphQLType<#scalar>>::name(info).unwrap(),
)
<Self as ::juniper::GraphQLType<#scalar>>::name(info)
.ok_or_else(|| ::juniper::macros::helper::err_unnamed_type(#ty_name))?,
)))
}
}
/// Returns generated code that panics about [GraphQL fields][1] tried to be
/// Returns generated code that errors about [GraphQL fields][1] tried to be
/// resolved asynchronously in the [`GraphQLValue::resolve_field`] method
/// (which is synchronous itself).
///
/// [`GraphQLValue::resolve_field`]: juniper::GraphQLValue::resolve_field
/// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields
#[must_use]
pub(crate) fn method_resolve_field_panic_async_field_tokens(
pub(crate) fn method_resolve_field_err_async_field_tokens(
field_names: &[&str],
scalar: &scalar::Type,
ty_name: &str,
) -> TokenStream {
quote! {
#( #field_names )|* => panic!(
#( #field_names )|* => return Err(::juniper::FieldError::from(format!(
"Tried to resolve async field `{}` on type `{}` with a sync resolver",
field,
<Self as ::juniper::GraphQLType<#scalar>>::name(info).unwrap(),
),
<Self as ::juniper::GraphQLType<#scalar>>::name(info)
.ok_or_else(|| ::juniper::macros::helper::err_unnamed_type(#ty_name))?,
))),
}
}
@ -409,7 +415,7 @@ impl Definition {
.as_ref()
.unwrap()
.iter()
.map(|arg| arg.method_resolve_field_tokens(scalar));
.map(|arg| arg.method_resolve_field_tokens(scalar, false));
let rcv = self.has_receiver.then(|| {
quote! { self, }
@ -455,7 +461,7 @@ impl Definition {
.as_ref()
.unwrap()
.iter()
.map(|arg| arg.method_resolve_field_tokens(scalar));
.map(|arg| arg.method_resolve_field_tokens(scalar, true));
let rcv = self.has_receiver.then(|| {
quote! { self, }
@ -504,7 +510,7 @@ impl Definition {
.as_ref()
.unwrap()
.iter()
.map(|arg| arg.method_resolve_field_tokens(scalar));
.map(|arg| arg.method_resolve_field_tokens(scalar, false));
let rcv = self.has_receiver.then(|| {
quote! { self, }

View file

@ -197,9 +197,13 @@ fn impl_scalar_struct(
where
#scalar: ::juniper::ScalarValue,
{
fn from_input_value(v: &::juniper::InputValue<#scalar>) -> Option<#ident> {
type Error = <#inner_ty as ::juniper::FromInputValue<#scalar>>::Error;
fn from_input_value(
v: &::juniper::InputValue<#scalar>
) -> Result<#ident, <#inner_ty as ::juniper::FromInputValue<#scalar>>::Error> {
let inner: #inner_ty = ::juniper::FromInputValue::<#scalar>::from_input_value(v)?;
Some(#ident(inner))
Ok(#ident(inner))
}
}

View file

@ -572,23 +572,27 @@ impl Definition {
let (impl_generics, where_clause) = self.ty.impl_generics(false);
let ty = self.ty.ty_tokens();
let ty_name = ty.to_string();
let trait_ty = self.ty.trait_ty();
let fields_resolvers = self
.fields
.iter()
.filter_map(|f| f.method_resolve_field_tokens(scalar, Some(&trait_ty)));
let async_fields_panic = {
let async_fields_err = {
let names = self
.fields
.iter()
.filter_map(|f| f.is_async.then(|| f.name.as_str()))
.collect::<Vec<_>>();
(!names.is_empty()).then(|| {
field::Definition::method_resolve_field_panic_async_field_tokens(&names, scalar)
field::Definition::method_resolve_field_err_async_field_tokens(
&names, scalar, &ty_name,
)
})
};
let no_field_panic = field::Definition::method_resolve_field_panic_no_field_tokens(scalar);
let no_field_err =
field::Definition::method_resolve_field_err_no_field_tokens(scalar, &ty_name);
let custom_downcast_checks = self
.implementers
@ -623,8 +627,8 @@ impl Definition {
) -> ::juniper::ExecutionResult<#scalar> {
match field {
#( #fields_resolvers )*
#async_fields_panic
_ => #no_field_panic,
#async_fields_err
_ => #no_field_err,
}
}
@ -662,13 +666,15 @@ impl Definition {
let (impl_generics, where_clause) = self.ty.impl_generics(true);
let ty = self.ty.ty_tokens();
let ty_name = ty.to_string();
let trait_ty = self.ty.trait_ty();
let fields_resolvers = self
.fields
.iter()
.map(|f| f.method_resolve_field_async_tokens(scalar, Some(&trait_ty)));
let no_field_panic = field::Definition::method_resolve_field_panic_no_field_tokens(scalar);
let no_field_err =
field::Definition::method_resolve_field_err_no_field_tokens(scalar, &ty_name);
let custom_downcasts = self
.implementers
@ -690,7 +696,7 @@ impl Definition {
) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> {
match field {
#( #fields_resolvers )*
_ => #no_field_panic,
_ => Box::pin(async move { #no_field_err }),
}
}
@ -840,6 +846,7 @@ impl Implementer {
self.downcast.as_ref()?;
let ty = &self.ty;
let ty_name = ty.to_token_stream().to_string();
let scalar = &self.scalar;
let downcast = self.downcast_call_tokens(trait_ty, None);
@ -847,7 +854,9 @@ impl Implementer {
let resolving_code = gen::sync_resolving_code();
Some(quote! {
if type_name == <#ty as ::juniper::GraphQLType<#scalar>>::name(info).unwrap() {
if type_name == <#ty as ::juniper::GraphQLType<#scalar>>::name(info)
.ok_or_else(|| ::juniper::macros::helper::err_unnamed_type(#ty_name))?
{
let res = #downcast;
return #resolving_code;
}
@ -868,6 +877,7 @@ impl Implementer {
self.downcast.as_ref()?;
let ty = &self.ty;
let ty_name = ty.to_token_stream().to_string();
let scalar = &self.scalar;
let downcast = self.downcast_call_tokens(trait_ty, None);
@ -875,9 +885,14 @@ impl Implementer {
let resolving_code = gen::async_resolving_code(None);
Some(quote! {
if type_name == <#ty as ::juniper::GraphQLType<#scalar>>::name(info).unwrap() {
let fut = ::juniper::futures::future::ready(#downcast);
return #resolving_code;
match <#ty as ::juniper::GraphQLType<#scalar>>::name(info) {
Some(name) => {
if type_name == name {
let fut = ::juniper::futures::future::ready(#downcast);
return #resolving_code;
}
}
None => return ::juniper::macros::helper::err_unnamed_type_fut(#ty_name),
}
})
}

View file

@ -486,6 +486,7 @@ impl Definition<Query> {
let (impl_generics, where_clause) = self.impl_generics(false);
let ty = &self.ty;
let ty_name = ty.to_token_stream().to_string();
let name = &self.name;
@ -493,17 +494,20 @@ impl Definition<Query> {
.fields
.iter()
.filter_map(|f| f.method_resolve_field_tokens(scalar, None));
let async_fields_panic = {
let async_fields_err = {
let names = self
.fields
.iter()
.filter_map(|f| f.is_async.then(|| f.name.as_str()))
.collect::<Vec<_>>();
(!names.is_empty()).then(|| {
field::Definition::method_resolve_field_panic_async_field_tokens(&names, scalar)
field::Definition::method_resolve_field_err_async_field_tokens(
&names, scalar, &ty_name,
)
})
};
let no_field_panic = field::Definition::method_resolve_field_panic_no_field_tokens(scalar);
let no_field_err =
field::Definition::method_resolve_field_err_no_field_tokens(scalar, &ty_name);
quote! {
#[allow(deprecated)]
@ -526,8 +530,8 @@ impl Definition<Query> {
) -> ::juniper::ExecutionResult<#scalar> {
match field {
#( #fields_resolvers )*
#async_fields_panic
_ => #no_field_panic,
#async_fields_err
_ => #no_field_err,
}
}
@ -553,12 +557,14 @@ impl Definition<Query> {
let (impl_generics, where_clause) = self.impl_generics(true);
let ty = &self.ty;
let ty_name = ty.to_token_stream().to_string();
let fields_resolvers = self
.fields
.iter()
.map(|f| f.method_resolve_field_async_tokens(scalar, None));
let no_field_panic = field::Definition::method_resolve_field_panic_no_field_tokens(scalar);
let no_field_err =
field::Definition::method_resolve_field_err_no_field_tokens(scalar, &ty_name);
quote! {
#[allow(deprecated, non_snake_case)]
@ -574,7 +580,7 @@ impl Definition<Query> {
) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> {
match field {
#( #fields_resolvers )*
_ => #no_field_panic,
_ => Box::pin(async move { #no_field_err }),
}
}
}

View file

@ -60,7 +60,9 @@ impl Definition<Subscription> {
_: &::juniper::Arguments<#scalar>,
_: &::juniper::Executor<Self::Context, #scalar>,
) -> ::juniper::ExecutionResult<#scalar> {
panic!("Called `resolve_field` on subscription object");
Err(::juniper::FieldError::from(
"Called `resolve_field` on subscription object",
))
}
fn concrete_type_name(
@ -95,12 +97,14 @@ impl Definition<Subscription> {
.push(parse_quote! { #scalar: Send + Sync });
}
let ty = &self.ty;
let ty_name = ty.to_token_stream().to_string();
let fields_resolvers = self
.fields
.iter()
.map(|f| f.method_resolve_field_into_stream_tokens(scalar));
let no_field_panic = field::Definition::method_resolve_field_panic_no_field_tokens(scalar);
let no_field_err =
field::Definition::method_resolve_field_err_no_field_tokens(scalar, &ty_name);
quote! {
#[allow(deprecated)]
@ -130,7 +134,7 @@ impl Definition<Subscription> {
{
match field {
#( #fields_resolvers )*
_ => #no_field_panic,
_ => Box::pin(async move { #no_field_err }),
}
}
}

View file

@ -558,11 +558,11 @@ impl Definition {
) -> ::juniper::ExecutionResult<#scalar> {
let context = executor.context();
#( #variant_resolvers )*
panic!(
return Err(::juniper::FieldError::from(format!(
"Concrete type `{}` is not handled by instance \
resolvers on GraphQL union `{}`",
type_name, #name,
);
)));
}
}
}
@ -600,11 +600,11 @@ impl Definition {
) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> {
let context = executor.context();
#( #variant_async_resolvers )*
panic!(
return ::juniper::macros::helper::err_fut(format!(
"Concrete type `{}` is not handled by instance \
resolvers on GraphQL union `{}`",
type_name, #name,
);
));
}
}
}
@ -670,11 +670,14 @@ impl VariantDefinition {
#[must_use]
fn method_resolve_into_type_tokens(&self, scalar: &scalar::Type) -> TokenStream {
let ty = &self.ty;
let ty_name = ty.to_token_stream().to_string();
let expr = &self.resolver_code;
let resolving_code = gen::sync_resolving_code();
quote! {
if type_name == <#ty as ::juniper::GraphQLType<#scalar>>::name(info).unwrap() {
if type_name == <#ty as ::juniper::GraphQLType<#scalar>>::name(info)
.ok_or_else(|| ::juniper::macros::helper::err_unnamed_type(#ty_name))?
{
let res = { #expr };
return #resolving_code;
}
@ -690,13 +693,19 @@ impl VariantDefinition {
#[must_use]
fn method_resolve_into_type_async_tokens(&self, scalar: &scalar::Type) -> TokenStream {
let ty = &self.ty;
let ty_name = ty.to_token_stream().to_string();
let expr = &self.resolver_code;
let resolving_code = gen::async_resolving_code(None);
quote! {
if type_name == <#ty as ::juniper::GraphQLType<#scalar>>::name(info).unwrap() {
let fut = ::juniper::futures::future::ready({ #expr });
return #resolving_code;
match <#ty as ::juniper::GraphQLType<#scalar>>::name(info) {
Some(name) => {
if type_name == name {
let fut = ::juniper::futures::future::ready({ #expr });
return #resolving_code;
}
}
None => return ::juniper::macros::helper::err_unnamed_type_fut(#ty_name),
}
}
}

View file

@ -309,6 +309,8 @@ pub fn build_scalar(
impl#generic_type_decl ::juniper::FromInputValue<#generic_type> for #impl_for_type
#generic_type_bound
{
type Error = <#from_input_value_result as ::juniper::macros::helper::ExtractError>::Error;
fn from_input_value(#from_input_value_arg: &::juniper::InputValue<#generic_type>) -> #from_input_value_result {
#from_input_value_body
}

View file

@ -230,8 +230,11 @@ pub fn derive_scalar_value(input: TokenStream) -> TokenStream {
/// juniper::Value::scalar(self.0.to_owned())
/// }
///
/// fn from_input_value(value: &juniper::InputValue) -> Option<UserID> {
/// value.as_string_value().map(|s| UserID(s.to_owned()))
/// // NOTE: The error type should implement `IntoFieldError<S>`.
/// fn from_input_value(value: &juniper::InputValue) -> Result<UserID, String> {
/// value.as_string_value()
/// .map(|s| UserID(s.to_owned()))
/// .ok_or_else(|| format!("Expected `String`, found: {}", value))
/// }
///
/// fn from_str<'a>(value: juniper::ScalarToken<'a>) -> juniper::ParseScalarResult<'a, S> {

View file

@ -752,7 +752,7 @@ impl GraphQLTypeDefiniton {
let resolver_code = &variant.resolver_code;
quote!(
Some(#variant_name) => Some(#resolver_code),
Some(#variant_name) => Ok(#resolver_code),
)
});
@ -860,13 +860,14 @@ impl GraphQLTypeDefiniton {
impl#impl_generics ::juniper::FromInputValue<#scalar> for #ty
#where_clause
{
fn from_input_value(v: &::juniper::InputValue<#scalar>) -> Option<#ty>
{
match v.as_enum_value().or_else(|| {
v.as_string_value()
}) {
type Error = ::std::string::String;
fn from_input_value(
v: &::juniper::InputValue<#scalar>
) -> Result<#ty, Self::Error> {
match v.as_enum_value().or_else(|| v.as_string_value()) {
#( #from_inputs )*
_ => None,
_ => Err(format!("Unknown enum value: {}", v)),
}
}
}
@ -980,8 +981,14 @@ impl GraphQLTypeDefiniton {
#field_ident: {
match obj.get(#field_name) {
#from_input_default
Some(ref v) => ::juniper::FromInputValue::from_input_value(v)?,
None => ::juniper::FromInputValue::<#scalar>::from_implicit_null()?,
Some(ref v) => {
::juniper::FromInputValue::<#scalar>::from_input_value(v)
.map_err(::juniper::IntoFieldError::into_field_error)?
},
None => {
::juniper::FromInputValue::<#scalar>::from_implicit_null()
.map_err(::juniper::IntoFieldError::into_field_error)?
},
}
},
)
@ -1096,10 +1103,17 @@ impl GraphQLTypeDefiniton {
impl#impl_generics ::juniper::FromInputValue<#scalar> for #ty #type_generics_tokens
#where_clause
{
fn from_input_value(value: &::juniper::InputValue<#scalar>) -> Option<Self>
{
let obj = value.to_object_value()?;
Some(#ty {
type Error = ::juniper::FieldError<#scalar>;
fn from_input_value(
value: &::juniper::InputValue<#scalar>
) -> Result<Self, Self::Error> {
let obj = value
.to_object_value()
.ok_or_else(|| ::juniper::FieldError::<#scalar>::from(
format!("Expected input object, found: {}", value))
)?;
Ok(#ty {
#( #from_inputs )*
})
}