Skip to content

Commit 5caea1f

Browse files
committed
Make FieldResults in field resolvers optional :D
1 parent f3469dd commit 5caea1f

File tree

20 files changed

+328
-258
lines changed

20 files changed

+328
-258
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ as well.
132132
* [X] `graphql_input_object!` helper completely missing
133133
* [X] Add support for deprecating things
134134
* [X] Custom enum values and descriptions
135-
* [ ] Improved syntax for fields that can't fail resolution - make
135+
* [X] Improved syntax for fields that can't fail resolution - make
136136
`FieldResult<T>` optional maybe?
137137
* [ ] Investigate asynchronous execution - implementing it is not necessary, but
138138
at least look at what API changes will be needed for us to hook into

src/executor.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,23 @@ pub type FieldResult<T> = Result<T, String>;
6161
/// The result of resolving an unspecified field
6262
pub type ExecutionResult = Result<Value, String>;
6363

64+
/// Convert a value into a successful field result
65+
///
66+
/// Used by the helper macros to support both returning a naked value
67+
/// *and* a `FieldResult` from a field.
68+
pub trait IntoFieldResult<T>: Sized {
69+
/// Wrap `self` in a `Result`
70+
///
71+
/// The implementation of this should always be `Ok(self)`.
72+
fn into(self) -> FieldResult<T>;
73+
}
74+
75+
impl<T> IntoFieldResult<T> for FieldResult<T> {
76+
fn into(self) -> FieldResult<T> {
77+
self
78+
}
79+
}
80+
6481
impl<'a, CtxT> Executor<'a, CtxT> {
6582
/// Resolve a single arbitrary value into an `ExecutionResult`
6683
pub fn resolve<T: GraphQLType<CtxT>>(&mut self, value: &T) -> ExecutionResult {
@@ -293,6 +310,17 @@ impl<CtxT> Registry<CtxT> {
293310
}
294311
}
295312

313+
#[doc(hidden)]
314+
pub fn field_convert<T: IntoFieldResult<I>, I>(&mut self, name: &str) -> Field where I: GraphQLType<CtxT> {
315+
Field {
316+
name: name.to_owned(),
317+
description: None,
318+
arguments: None,
319+
field_type: self.get_type::<I>(),
320+
deprecation_reason: None,
321+
}
322+
}
323+
296324
#[doc(hidden)]
297325
pub fn field_inside_result<T>(&mut self, name: &str, _: FieldResult<T>) -> Field where T: GraphQLType<CtxT> {
298326
Field {

src/executor_tests/introspection.rs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use std::collections::HashMap;
22

3-
use executor::FieldResult;
43
use value::Value;
54
use schema::model::RootNode;
65

@@ -32,9 +31,9 @@ graphql_enum!(Sample as "SampleEnum" {
3231

3332
graphql_interface!(Interface: () as "SampleInterface" |&self| {
3433
description: "A sample interface"
35-
36-
field sample_enum() -> FieldResult<Sample> as "A sample field in the interface" {
37-
Ok(Sample::One)
34+
35+
field sample_enum() -> Sample as "A sample field in the interface" {
36+
Sample::One
3837
}
3938

4039
instance_resolvers: |&_| [
@@ -47,15 +46,15 @@ graphql_object!(Root: () as "Root" |&self| {
4746

4847
interfaces: [Interface]
4948

50-
field sample_enum() -> FieldResult<Sample> {
51-
Ok(Sample::One)
49+
field sample_enum() -> Sample {
50+
Sample::One
5251
}
5352

5453
field sample_scalar(
55-
first: i64 as "The first number",
54+
first: i64 as "The first number",
5655
second = 123: i64 as "The second number"
57-
) -> FieldResult<Scalar> as "A sample scalar field on the object" {
58-
Ok(Scalar(first + second))
56+
) -> Scalar as "A sample scalar field on the object" {
57+
Scalar(first + second)
5958
}
6059
});
6160

@@ -121,7 +120,7 @@ fn enum_introspection() {
121120

122121
assert_eq!(type_info.get("name"), Some(&Value::string("SampleEnum")));
123122
assert_eq!(type_info.get("kind"), Some(&Value::string("ENUM")));
124-
assert_eq!(type_info.get("description"), Some(&Value::null()));
123+
assert_eq!(type_info.get("description"), Some(&Value::null()));
125124
assert_eq!(type_info.get("interfaces"), Some(&Value::null()));
126125
assert_eq!(type_info.get("possibleTypes"), Some(&Value::null()));
127126
assert_eq!(type_info.get("inputFields"), Some(&Value::null()));

src/lib.rs

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ existing object types as GraphQL objects:
2828
2929
```rust
3030
#[macro_use] extern crate juniper;
31-
use juniper::FieldResult;
3231
# use std::collections::HashMap;
32+
use juniper::FieldResult;
3333
3434
struct User { id: String, name: String, friend_ids: Vec<String> }
3535
struct QueryRoot;
@@ -42,15 +42,19 @@ struct Database { users: HashMap<String, User> }
4242
graphql_object!(User: Database as "User" |&self| {
4343
4444
// Expose a simple field as a GraphQL string.
45+
field id() -> &String {
46+
&self.id
47+
}
48+
49+
field name() -> &String {
50+
&self.name
51+
}
52+
4553
// FieldResult<T> is an alias for Result<T, String> - simply return
4654
// a string from this method and it will be correctly inserted into
4755
// the execution response.
48-
field id() -> FieldResult<&String> {
49-
Ok(&self.id)
50-
}
51-
52-
field name() -> FieldResult<&String> {
53-
Ok(&self.name)
56+
field secret() -> FieldResult<&String> {
57+
Err("Can't touch this".to_owned())
5458
}
5559
5660
// Field accessors can optionally take an "executor" as their first
@@ -59,10 +63,10 @@ graphql_object!(User: Database as "User" |&self| {
5963
//
6064
// In this example, the context is used to convert the friend_ids array
6165
// into actual User objects.
62-
field friends(&mut executor) -> FieldResult<Vec<&User>> {
63-
Ok(self.friend_ids.iter()
66+
field friends(&mut executor) -> Vec<&User> {
67+
self.friend_ids.iter()
6468
.filter_map(|id| executor.context().users.get(id))
65-
.collect())
69+
.collect()
6670
}
6771
});
6872
@@ -71,8 +75,8 @@ graphql_object!(User: Database as "User" |&self| {
7175
graphql_object!(QueryRoot: Database as "Query" |&self| {
7276
7377
// Arguments work just like they do on functions.
74-
field user(&mut executor, id: String) -> FieldResult<Option<&User>> {
75-
Ok(executor.context().users.get(&id))
78+
field user(&mut executor, id: String) -> Option<&User> {
79+
executor.context().users.get(&id)
7680
}
7781
});
7882
@@ -197,13 +201,13 @@ use rustc_serialize::json::{ToJson, Json};
197201

198202
use parser::{parse_document_source, ParseError, Spanning, SourcePosition};
199203
use validation::{RuleError, ValidatorContext, visit_all_rules};
204+
use executor::execute_validated_query;
200205

201206
pub use ast::{ToInputValue, FromInputValue, InputValue, Type, Selection};
202207
pub use value::Value;
203208
pub use types::base::{Arguments, GraphQLType, TypeKind};
204209
pub use executor::{
205-
Executor, Registry, ExecutionResult, ExecutionError, FieldResult,
206-
execute_validated_query,
210+
Executor, Registry, ExecutionResult, ExecutionError, FieldResult, IntoFieldResult,
207211
};
208212
pub use types::scalars::ID;
209213
pub use schema::model::RootNode;

src/macros/enums.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,12 @@ macro_rules! graphql_enum {
110110
}
111111
}
112112
}
113+
114+
impl $crate::IntoFieldResult<$name> for $name {
115+
fn into(self) -> $crate::FieldResult<$name> {
116+
Ok(self)
117+
}
118+
}
113119
};
114120

115121
// No more items to parse

src/macros/field.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ macro_rules! __graphql__build_field_matches {
7777
$body
7878
};
7979

80-
return result.and_then(|r| $executorvar.resolve(&r))
80+
return ($crate::IntoFieldResult::into(result)).and_then(|r| $executorvar.resolve(&r))
8181
}
8282
)*
8383
panic!("Field {} not found on type {}", $fieldvar, $outname);

src/macros/interface.rs

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ shared context to implement downcasts.
3838
3939
```rust
4040
# #[macro_use] extern crate juniper;
41-
# use juniper::FieldResult;
4241
# use std::collections::HashMap;
4342
struct Human { id: String }
4443
struct Droid { id: String }
@@ -60,16 +59,16 @@ impl Character for Droid {
6059
}
6160
6261
graphql_object!(Human: Database as "Human" |&self| {
63-
field id() -> FieldResult<&str> { Ok(&self.id) }
62+
field id() -> &str { &self.id }
6463
});
6564
6665
graphql_object!(Droid: Database as "Droid" |&self| {
67-
field id() -> FieldResult<&str> { Ok(&self.id) }
66+
field id() -> &str { &self.id }
6867
});
6968
7069
// You can introduce lifetimes or generic parameters by < > before the name.
7170
graphql_interface!(<'a> &'a Character: Database as "Character" |&self| {
72-
field id() -> FieldResult<&str> { Ok(self.id()) }
71+
field id() -> &str { self.id() }
7372
7473
instance_resolvers: |&context| [
7574
context.humans.get(self.id()),
@@ -97,9 +96,8 @@ macro_rules! graphql_interface {
9796
$acc.push(__graphql__args!(
9897
@apply_args,
9998
$reg,
100-
$reg.field_inside_result(
101-
&$crate::to_snake_case(stringify!($name)),
102-
Err("dummy".to_owned()) as $t)
99+
$reg.field_convert::<$t, _>(
100+
&$crate::to_snake_case(stringify!($name)))
103101
.description($desc)
104102
.deprecated($reason),
105103
$args));
@@ -116,9 +114,8 @@ macro_rules! graphql_interface {
116114
$acc.push(__graphql__args!(
117115
@apply_args,
118116
$reg,
119-
$reg.field_inside_result(
120-
&$crate::to_snake_case(stringify!($name)),
121-
Err("dummy".to_owned()) as $t)
117+
$reg.field_convert::<$t, _>(
118+
&$crate::to_snake_case(stringify!($name)))
122119
.deprecated($reason),
123120
$args));
124121

@@ -134,9 +131,8 @@ macro_rules! graphql_interface {
134131
$acc.push(__graphql__args!(
135132
@apply_args,
136133
$reg,
137-
$reg.field_inside_result(
138-
&$crate::to_snake_case(stringify!($name)),
139-
Err("dummy".to_owned()) as $t)
134+
$reg.field_convert::<$t, _>(
135+
&$crate::to_snake_case(stringify!($name)))
140136
.description($desc),
141137
$args));
142138

@@ -152,9 +148,8 @@ macro_rules! graphql_interface {
152148
$acc.push(__graphql__args!(
153149
@apply_args,
154150
$reg,
155-
$reg.field_inside_result(
156-
&$crate::to_snake_case(stringify!($name)),
157-
Err("dummy".to_owned()) as $t),
151+
$reg.field_convert::<$t, _>(
152+
&$crate::to_snake_case(stringify!($name))),
158153
$args));
159154

160155
graphql_interface!(@gather_meta, $reg, $acc, $descr, $( $rest )*);

0 commit comments

Comments
 (0)