Skip to content

Commit 46be97a

Browse files
tyranronilslv
andauthored
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 <[email protected]>
1 parent e264cf5 commit 46be97a

File tree

96 files changed

+1306
-838
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

96 files changed

+1306
-838
lines changed

docs/book/content/types/scalars.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ The example below is used just for illustration.
113113
# }
114114
# }
115115
# }
116-
116+
#
117117
use juniper::{Value, ParseScalarResult, ParseScalarValue};
118118
use date::Date;
119119

@@ -128,17 +128,18 @@ where
128128
}
129129

130130
// Define how to parse a primitive type into your custom scalar.
131-
fn from_input_value(v: &InputValue) -> Option<Date> {
132-
v.as_scalar_value()
133-
.and_then(|v| v.as_str())
134-
.and_then(|s| s.parse().ok())
131+
// NOTE: The error type should implement `IntoFieldError<S>`.
132+
fn from_input_value(v: &InputValue) -> Result<Date, String> {
133+
v.as_string_value()
134+
.ok_or_else(|| format!("Expected `String`, found: {}", v))
135+
.and_then(|s| s.parse().map_err(|e| format!("Failed to parse `Date`: {}", e)))
135136
}
136137

137138
// Define how to parse a string value.
138139
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
139140
<String as ParseScalarValue<S>>::from_str(value)
140141
}
141142
}
142-
143+
#
143144
# fn main() {}
144145
```

integration_tests/async_await/src/main.rs

Lines changed: 71 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,18 @@ impl Query {
7070
}
7171
}
7272

73-
#[tokio::test]
74-
async fn async_simple() {
75-
let schema = RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new());
76-
let doc = r#"
77-
query {
73+
fn main() {}
74+
75+
#[cfg(test)]
76+
mod tests {
77+
use juniper::{graphql_value, EmptyMutation, EmptySubscription, GraphQLError, RootNode, Value};
78+
79+
use super::Query;
80+
81+
#[tokio::test]
82+
async fn async_simple() {
83+
let schema = RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new());
84+
let doc = r#"query {
7885
fieldSync
7986
fieldAsyncPlain
8087
delayed
@@ -83,39 +90,37 @@ async fn async_simple() {
8390
name
8491
delayed
8592
}
86-
}
87-
"#;
93+
}"#;
8894

89-
let vars = Default::default();
90-
let (res, errs) = juniper::execute(doc, None, &schema, &vars, &())
91-
.await
92-
.unwrap();
95+
let vars = Default::default();
96+
let (res, errs) = juniper::execute(doc, None, &schema, &vars, &())
97+
.await
98+
.unwrap();
9399

94-
assert!(errs.is_empty());
100+
assert!(errs.is_empty());
95101

96-
let obj = res.into_object().unwrap();
97-
let value = Value::Object(obj);
102+
let obj = res.into_object().unwrap();
103+
let value = Value::Object(obj);
98104

99-
assert_eq!(
100-
value,
101-
graphql_value!({
102-
"delayed": true,
103-
"fieldAsyncPlain": "field_async_plain",
104-
"fieldSync": "field_sync",
105-
"user": {
105+
assert_eq!(
106+
value,
107+
graphql_value!({
106108
"delayed": true,
107-
"kind": "USER",
108-
"name": "user1",
109-
},
110-
}),
111-
);
112-
}
109+
"fieldAsyncPlain": "field_async_plain",
110+
"fieldSync": "field_sync",
111+
"user": {
112+
"delayed": true,
113+
"kind": "USER",
114+
"name": "user1",
115+
},
116+
}),
117+
);
118+
}
113119

114-
#[tokio::test]
115-
async fn async_field_validation_error() {
116-
let schema = RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new());
117-
let doc = r#"
118-
query {
120+
#[tokio::test]
121+
async fn async_field_validation_error() {
122+
let schema = RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new());
123+
let doc = r#"query {
119124
nonExistentField
120125
fieldSync
121126
fieldAsyncPlain
@@ -125,40 +130,38 @@ async fn async_field_validation_error() {
125130
name
126131
delayed
127132
}
128-
}
129-
"#;
130-
131-
let vars = Default::default();
132-
let result = juniper::execute(doc, None, &schema, &vars, &()).await;
133-
assert!(result.is_err());
134-
135-
let error = result.err().unwrap();
136-
let is_validation_error = match error {
137-
GraphQLError::ValidationError(_) => true,
138-
_ => false,
139-
};
140-
assert!(is_validation_error);
141-
}
142-
143-
// FIXME: test seems broken by design, re-enable later
144-
// #[tokio::test]
145-
// async fn resolve_into_stream_validation_error() {
146-
// let schema = RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new());
147-
// let doc = r#"
148-
// subscription {
149-
// nonExistent
150-
// }
151-
// "#;
152-
// let vars = Default::default();
153-
// let result = juniper::resolve_into_stream(doc, None, &schema, &vars, &()).await;
154-
// assert!(result.is_err());
155-
156-
// let error = result.err().unwrap();
157-
// let is_validation_error = match error {
158-
// GraphQLError::ValidationError(_) => true,
159-
// _ => false,
160-
// };
161-
// assert!(is_validation_error);
162-
// }
133+
}"#;
134+
135+
let vars = Default::default();
136+
let result = juniper::execute(doc, None, &schema, &vars, &()).await;
137+
assert!(result.is_err());
138+
139+
let error = result.err().unwrap();
140+
let is_validation_error = match error {
141+
GraphQLError::ValidationError(_) => true,
142+
_ => false,
143+
};
144+
assert!(is_validation_error);
145+
}
163146

164-
fn main() {}
147+
// FIXME: test seems broken by design, re-enable later
148+
// #[tokio::test]
149+
// async fn resolve_into_stream_validation_error() {
150+
// let schema = RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new());
151+
// let doc = r#"
152+
// subscription {
153+
// nonExistent
154+
// }
155+
// "#;
156+
// let vars = Default::default();
157+
// let result = juniper::resolve_into_stream(doc, None, &schema, &vars, &()).await;
158+
// assert!(result.is_err());
159+
160+
// let error = result.err().unwrap();
161+
// let is_validation_error = match error {
162+
// GraphQLError::ValidationError(_) => true,
163+
// _ => false,
164+
// };
165+
// assert!(is_validation_error);
166+
// }
167+
}
Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
error: GraphQL enum expects at least one field
2-
--> $DIR/derive_no_fields.rs:2:1
2+
3+
= note: https://spec.graphql.org/June2018/#sec-Enums
4+
5+
--> fail/enum/derive_no_fields.rs:2:1
36
|
47
2 | pub enum Test {}
5-
| ^^^^^^^^^^^^^^^^
6-
|
7-
= note: https://spec.graphql.org/June2018/#sec-Enums
8+
| ^^^

integration_tests/codegen_fail/fail/input-object/derive_incompatible_object.stderr

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,39 @@
11
error[E0277]: the trait bound `ObjectA: IsInputType<__S>` is not satisfied
2-
--> $DIR/derive_incompatible_object.rs:6:10
3-
|
4-
6 | #[derive(juniper::GraphQLInputObject)]
5-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsInputType<__S>` is not implemented for `ObjectA`
6-
|
7-
= note: required by `juniper::marker::IsInputType::mark`
8-
= note: this error originates in the derive macro `juniper::GraphQLInputObject` (in Nightly builds, run with -Z macro-backtrace for more info)
2+
--> fail/input-object/derive_incompatible_object.rs:6:10
3+
|
4+
6 | #[derive(juniper::GraphQLInputObject)]
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsInputType<__S>` is not implemented for `ObjectA`
6+
|
7+
note: required by `juniper::marker::IsInputType::mark`
8+
--> $WORKSPACE/juniper/src/types/marker.rs
9+
|
10+
| fn mark() {}
11+
| ^^^^^^^^^
12+
= note: this error originates in the derive macro `juniper::GraphQLInputObject` (in Nightly builds, run with -Z macro-backtrace for more info)
913

1014
error[E0277]: the trait bound `ObjectA: FromInputValue<__S>` is not satisfied
11-
--> $DIR/derive_incompatible_object.rs:6:10
15+
--> fail/input-object/derive_incompatible_object.rs:6:10
1216
|
1317
6 | #[derive(juniper::GraphQLInputObject)]
1418
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjectA`
1519
|
1620
= note: this error originates in the derive macro `juniper::GraphQLInputObject` (in Nightly builds, run with -Z macro-backtrace for more info)
1721

1822
error[E0277]: the trait bound `ObjectA: FromInputValue<__S>` is not satisfied
19-
--> $DIR/derive_incompatible_object.rs:6:10
20-
|
21-
6 | #[derive(juniper::GraphQLInputObject)]
22-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjectA`
23-
|
24-
= note: required by `from_input_value`
25-
= note: this error originates in the derive macro `juniper::GraphQLInputObject` (in Nightly builds, run with -Z macro-backtrace for more info)
23+
--> fail/input-object/derive_incompatible_object.rs:6:10
24+
|
25+
6 | #[derive(juniper::GraphQLInputObject)]
26+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjectA`
27+
|
28+
note: required by `from_input_value`
29+
--> $WORKSPACE/juniper/src/ast.rs
30+
|
31+
| fn from_input_value(v: &InputValue<S>) -> Result<Self, Self::Error>;
32+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
33+
= note: this error originates in the derive macro `juniper::GraphQLInputObject` (in Nightly builds, run with -Z macro-backtrace for more info)
2634

2735
error[E0599]: no method named `to_input_value` found for struct `ObjectA` in the current scope
28-
--> $DIR/derive_incompatible_object.rs:6:10
36+
--> fail/input-object/derive_incompatible_object.rs:6:10
2937
|
3038
2 | struct ObjectA {
3139
| -------------- method `to_input_value` not found for this
Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
error: GraphQL input object expects at least one field
2-
--> $DIR/derive_no_fields.rs:2:1
2+
3+
= note: https://spec.graphql.org/June2018/#sec-Input-Objects
4+
5+
--> fail/input-object/derive_no_fields.rs:2:1
36
|
47
2 | struct Object {}
5-
| ^^^^^^^^^^^^^^^^
6-
|
7-
= note: https://spec.graphql.org/June2018/#sec-Input-Objects
8+
| ^^^^^^

integration_tests/codegen_fail/fail/input-object/derive_no_underscore.stderr

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
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 GraphQL’s introspection system.
2-
--> $DIR/derive_no_underscore.rs:3:15
2+
3+
= note: https://spec.graphql.org/June2018/#sec-Schema
4+
5+
--> fail/input-object/derive_no_underscore.rs:3:15
36
|
47
3 | #[graphql(name = "__test")]
58
| ^^^^
6-
|
7-
= note: https://spec.graphql.org/June2018/#sec-Schema
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
error: GraphQL input object does not allow fields with the same name
2-
--> $DIR/derive_unique_name.rs:4:5
3-
|
4-
4 | / #[graphql(name = "test")]
5-
5 | | test2: String,
6-
| |_________________^
7-
|
2+
83
= help: There is at least one other field with the same name `test`, possibly renamed via the #[graphql] attribute
94
= note: https://spec.graphql.org/June2018/#sec-Input-Objects
5+
6+
--> fail/input-object/derive_unique_name.rs:4:5
7+
|
8+
4 | #[graphql(name = "test")]
9+
| ^

integration_tests/codegen_fail/fail/interface/argument_double_underscored.stderr

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
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 GraphQL’s introspection system.
2+
3+
= note: https://spec.graphql.org/June2018/#sec-Schema
4+
25
--> $DIR/argument_double_underscored.rs:14:18
36
|
47
14 | fn id(&self, __num: i32) -> &str {
58
| ^^^^^
6-
|
7-
= note: https://spec.graphql.org/June2018/#sec-Schema
89

910
error[E0412]: cannot find type `CharacterValue` in this scope
1011
--> $DIR/argument_double_underscored.rs:4:18

integration_tests/codegen_fail/fail/interface/argument_non_input_type.stderr

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
error[E0277]: the trait bound `ObjA: IsInputType<__S>` is not satisfied
2-
--> $DIR/argument_non_input_type.rs:16:1
3-
|
4-
16 | #[graphql_interface(for = ObjA)]
5-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsInputType<__S>` is not implemented for `ObjA`
6-
|
7-
= note: required by `juniper::marker::IsInputType::mark`
8-
= note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info)
2+
--> fail/interface/argument_non_input_type.rs:16:1
3+
|
4+
16 | #[graphql_interface(for = ObjA)]
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsInputType<__S>` is not implemented for `ObjA`
6+
|
7+
note: required by `juniper::marker::IsInputType::mark`
8+
--> $WORKSPACE/juniper/src/types/marker.rs
9+
|
10+
| fn mark() {}
11+
| ^^^^^^^^^
12+
= note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info)
913

1014
error[E0277]: the trait bound `ObjA: FromInputValue<__S>` is not satisfied
11-
--> $DIR/argument_non_input_type.rs:16:1
15+
--> fail/interface/argument_non_input_type.rs:16:1
1216
|
1317
16 | #[graphql_interface(for = ObjA)]
1418
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjA`

integration_tests/codegen_fail/fail/interface/downcast_method_conflicts_with_external_downcast_fn.stderr

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
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`
2+
3+
= note: https://spec.graphql.org/June2018/#sec-Interfaces
4+
= note: use `#[graphql(ignore)]` attribute argument to ignore this trait method for interface implementers downcasting
5+
26
--> $DIR/downcast_method_conflicts_with_external_downcast_fn.rs:26:5
37
|
48
26 | fn as_obja(&self) -> Option<&ObjA>;
5-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6-
|
7-
= note: https://spec.graphql.org/June2018/#sec-Interfaces
8-
= note: use `#[graphql(ignore)]` attribute argument to ignore this trait method for interface implementers downcasting
9+
| ^^
910

1011
error[E0412]: cannot find type `CharacterValue` in this scope
1112
--> $DIR/downcast_method_conflicts_with_external_downcast_fn.rs:4:18

integration_tests/codegen_fail/fail/interface/downcast_method_wrong_input_args.stderr

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
error: GraphQL interface expects trait method to accept `&self` only and, optionally, `&Context`
2+
3+
= note: https://spec.graphql.org/June2018/#sec-Interfaces
4+
25
--> $DIR/downcast_method_wrong_input_args.rs:10:10
36
|
47
10 | fn a(&self, ctx: &(), rand: u8) -> Option<&Human> {
5-
| ^^^^^^^^^^^^^^^^^^^^^^^^^
6-
|
7-
= note: https://spec.graphql.org/June2018/#sec-Interfaces
8+
| ^
89

910
error[E0412]: cannot find type `CharacterValue` in this scope
1011
--> $DIR/downcast_method_wrong_input_args.rs:16:18

0 commit comments

Comments
 (0)