Skip to content

Commit ebda74b

Browse files
committed
Refactor IntoResolvable (previously IntoFieldResult)
It now supports a blanket implementation for all GraphQLTypes, and resolving into new context types.
1 parent 6b8f4c9 commit ebda74b

File tree

12 files changed

+286
-128
lines changed

12 files changed

+286
-128
lines changed

src/executor.rs

Lines changed: 54 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -61,46 +61,79 @@ 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>;
64+
#[doc(hidden)]
65+
pub trait IntoResolvable<'a, T: GraphQLType, C>: Sized {
66+
#[doc(hidden)]
67+
fn into(self, ctx: &'a C) -> FieldResult<Option<(&'a T::Context, T)>>;
68+
}
69+
70+
impl<'a, T: GraphQLType, C> IntoResolvable<'a, T, C> for T where T::Context: FromContext<C> {
71+
fn into(self, ctx: &'a C) -> FieldResult<Option<(&'a T::Context, T)>> {
72+
Ok(Some((FromContext::from(ctx), self)))
73+
}
74+
}
75+
76+
impl<'a, T: GraphQLType, C> IntoResolvable<'a, T, C> for FieldResult<T> where T::Context: FromContext<C> {
77+
fn into(self, ctx: &'a C) -> FieldResult<Option<(&'a T::Context, T)>> {
78+
self.map(|v| Some((FromContext::from(ctx), v)))
79+
}
80+
}
81+
82+
impl<'a, T: GraphQLType, C> IntoResolvable<'a, T, C> for (&'a T::Context, T) {
83+
fn into(self, _: &'a C) -> FieldResult<Option<(&'a T::Context, T)>> {
84+
Ok(Some(self))
85+
}
7386
}
7487

75-
impl<T> IntoFieldResult<T> for FieldResult<T> {
76-
fn into(self) -> FieldResult<T> {
88+
impl<'a, T: GraphQLType, C> IntoResolvable<'a, T, C> for Option<(&'a T::Context, T)> {
89+
fn into(self, _: &'a C) -> FieldResult<Option<(&'a T::Context, T)>> {
90+
Ok(self)
91+
}
92+
}
93+
94+
impl<'a, T: GraphQLType, C> IntoResolvable<'a, T, C> for FieldResult<(&'a T::Context, T)> {
95+
fn into(self, _: &'a C) -> FieldResult<Option<(&'a T::Context, T)>> {
96+
self.map(|v| Some(v))
97+
}
98+
}
99+
100+
impl<'a, T: GraphQLType, C> IntoResolvable<'a, T, C> for FieldResult<Option<(&'a T::Context, T)>> {
101+
fn into(self, _: &'a C) -> FieldResult<Option<(&'a T::Context, T)>> {
77102
self
78103
}
79104
}
80105

81106
/// Conversion trait for context types
82107
///
83-
/// This is currently only used for converting arbitrary contexts into
84-
/// the empty tuple, but will in the future be used to support general
85-
/// context conversion for larger schemas.
108+
/// Used to support different context types for different parts of an
109+
/// application. By making each GraphQL type only aware of as much
110+
/// context as it needs to, isolation and robustness can be
111+
/// improved. Implement this trait if you have contexts that can
112+
/// generally be converted between each other.
113+
///
114+
/// The empty tuple `()` can be converted into from any context type,
115+
/// making it suitable for GraphQL that don't need _any_ context to
116+
/// work, e.g. scalars or enums.
86117
pub trait FromContext<T> {
87118
/// Perform the conversion
88-
fn from_context(value: &T) -> &Self;
119+
fn from(value: &T) -> &Self;
89120
}
90121

91122
/// Marker trait for types that can act as context objects for GraphQL types.
92123
pub trait Context { }
93124

125+
impl<'a, C: Context> Context for &'a C {}
126+
94127
static NULL_CONTEXT: () = ();
95128

96129
impl<T> FromContext<T> for () {
97-
fn from_context(_: &T) -> &Self {
130+
fn from(_: &T) -> &Self {
98131
&NULL_CONTEXT
99132
}
100133
}
101134

102135
impl<T> FromContext<T> for T where T: Context {
103-
fn from_context(value: &T) -> &Self {
136+
fn from(value: &T) -> &Self {
104137
value
105138
}
106139
}
@@ -112,7 +145,7 @@ impl<'a, CtxT> Executor<'a, CtxT> {
112145
) -> ExecutionResult
113146
where NewCtxT: FromContext<CtxT>,
114147
{
115-
self.replaced_context(<NewCtxT as FromContext<CtxT>>::from_context(&self.context))
148+
self.replaced_context(<NewCtxT as FromContext<CtxT>>::from(&self.context))
116149
.resolve(value)
117150
}
118151

@@ -363,7 +396,9 @@ impl Registry {
363396
}
364397

365398
#[doc(hidden)]
366-
pub fn field_convert<T: IntoFieldResult<I>, I>(&mut self, name: &str) -> Field where I: GraphQLType {
399+
pub fn field_convert<'a, T: IntoResolvable<'a, I, C>, I, C>(&mut self, name: &str) -> Field
400+
where I: GraphQLType
401+
{
367402
Field {
368403
name: name.to_owned(),
369404
description: None,
@@ -373,17 +408,6 @@ impl Registry {
373408
}
374409
}
375410

376-
#[doc(hidden)]
377-
pub fn field_inside_result<T>(&mut self, name: &str, _: FieldResult<T>) -> Field where T: GraphQLType {
378-
Field {
379-
name: name.to_owned(),
380-
description: None,
381-
arguments: None,
382-
field_type: self.get_type::<T>(),
383-
deprecation_reason: None,
384-
}
385-
}
386-
387411
/// Create an argument with the provided name
388412
pub fn arg<T>(&mut self, name: &str) -> Argument where T: GraphQLType + FromInputValue {
389413
Argument::new(name, self.get_type::<T>())

src/executor_tests/executor.rs

Lines changed: 215 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -166,21 +166,29 @@ mod threads_context_correctly {
166166
use value::Value;
167167
use types::scalars::EmptyMutation;
168168
use schema::model::RootNode;
169+
use executor::Context;
169170

170171
struct Schema;
171172

172-
graphql_object!(Schema: String |&self| {
173-
field a(&executor) -> String { executor.context().clone() }
173+
struct TestContext {
174+
value: String,
175+
}
176+
177+
impl Context for TestContext {}
178+
179+
graphql_object!(Schema: TestContext |&self| {
180+
field a(&executor) -> String { executor.context().value.clone() }
174181
});
175182

176183
#[test]
177184
fn test() {
178-
let schema = RootNode::new(Schema, EmptyMutation::<String>::new());
185+
let schema = RootNode::new(Schema, EmptyMutation::<TestContext>::new());
179186
let doc = r"{ a }";
180187

181188
let vars = vec![].into_iter().collect();
182189

183-
let (result, errs) = ::execute(doc, None, &schema, &vars, &"Context value".to_owned())
190+
let (result, errs) = ::execute(doc, None, &schema, &vars,
191+
&TestContext { value: "Context value".to_owned() })
184192
.expect("Execution failed");
185193

186194
assert_eq!(errs, []);
@@ -195,6 +203,209 @@ mod threads_context_correctly {
195203
}
196204
}
197205

206+
mod dynamic_context_switching {
207+
use std::collections::HashMap;
208+
209+
use value::Value;
210+
use types::scalars::EmptyMutation;
211+
use schema::model::RootNode;
212+
use parser::SourcePosition;
213+
use executor::{FieldResult, Context, ExecutionError};
214+
215+
struct Schema;
216+
217+
struct InnerContext {
218+
value: String,
219+
}
220+
221+
struct OuterContext {
222+
items: HashMap<i64, InnerContext>,
223+
}
224+
225+
impl Context for OuterContext {}
226+
impl Context for InnerContext {}
227+
228+
struct ItemRef;
229+
230+
graphql_object!(Schema: OuterContext |&self| {
231+
field item_opt(&executor, key: i64) -> Option<(&InnerContext, ItemRef)> {
232+
executor.context().items.get(&key).map(|c| (c, ItemRef))
233+
}
234+
235+
field item_res(&executor, key: i64) -> FieldResult<(&InnerContext, ItemRef)> {
236+
executor.context().items.get(&key)
237+
.ok_or(format!("Could not find key {}", key))
238+
.map(|c| (c, ItemRef))
239+
}
240+
241+
field item_res_opt(&executor, key: i64) -> FieldResult<Option<(&InnerContext, ItemRef)>> {
242+
if key > 100 {
243+
Err(format!("Key too large: {}", key))
244+
} else {
245+
Ok(executor.context().items.get(&key)
246+
.map(|c| (c, ItemRef)))
247+
}
248+
}
249+
250+
field item_always(&executor, key: i64) -> (&InnerContext, ItemRef) {
251+
executor.context().items.get(&key)
252+
.map(|c| (c, ItemRef))
253+
.unwrap()
254+
}
255+
});
256+
257+
graphql_object!(ItemRef: InnerContext |&self| {
258+
field value(&executor) -> String { executor.context().value.clone() }
259+
});
260+
261+
#[test]
262+
fn test_opt() {
263+
let schema = RootNode::new(Schema, EmptyMutation::<OuterContext>::new());
264+
let doc = r"{ first: itemOpt(key: 0) { value }, missing: itemOpt(key: 2) { value } }";
265+
266+
let vars = vec![].into_iter().collect();
267+
268+
let ctx = OuterContext {
269+
items: vec![
270+
(0, InnerContext { value: "First value".to_owned() }),
271+
(1, InnerContext { value: "Second value".to_owned() }),
272+
].into_iter().collect(),
273+
};
274+
275+
let (result, errs) = ::execute(doc, None, &schema, &vars, &ctx)
276+
.expect("Execution failed");
277+
278+
assert_eq!(errs, []);
279+
280+
println!("Result: {:?}", result);
281+
282+
assert_eq!(
283+
result,
284+
Value::object(vec![
285+
("first", Value::object(vec![
286+
("value", Value::string("First value")),
287+
].into_iter().collect())),
288+
("missing", Value::null()),
289+
].into_iter().collect()));
290+
}
291+
292+
#[test]
293+
fn test_res() {
294+
let schema = RootNode::new(Schema, EmptyMutation::<OuterContext>::new());
295+
let doc = r"
296+
{
297+
first: itemRes(key: 0) { value }
298+
missing: itemRes(key: 2) { value }
299+
}
300+
";
301+
302+
let vars = vec![].into_iter().collect();
303+
304+
let ctx = OuterContext {
305+
items: vec![
306+
(0, InnerContext { value: "First value".to_owned() }),
307+
(1, InnerContext { value: "Second value".to_owned() }),
308+
].into_iter().collect(),
309+
};
310+
311+
let (result, errs) = ::execute(doc, None, &schema, &vars, &ctx)
312+
.expect("Execution failed");
313+
314+
assert_eq!(errs, vec![
315+
ExecutionError::new(
316+
SourcePosition::new(70, 3, 12),
317+
&["missing"],
318+
"Could not find key 2",
319+
),
320+
]);
321+
322+
println!("Result: {:?}", result);
323+
324+
assert_eq!(
325+
result,
326+
Value::object(vec![
327+
("first", Value::object(vec![
328+
("value", Value::string("First value")),
329+
].into_iter().collect())),
330+
("missing", Value::null()),
331+
].into_iter().collect()));
332+
}
333+
334+
#[test]
335+
fn test_res_opt() {
336+
let schema = RootNode::new(Schema, EmptyMutation::<OuterContext>::new());
337+
let doc = r"
338+
{
339+
first: itemResOpt(key: 0) { value }
340+
missing: itemResOpt(key: 2) { value }
341+
tooLarge: itemResOpt(key: 200) { value }
342+
}
343+
";
344+
345+
let vars = vec![].into_iter().collect();
346+
347+
let ctx = OuterContext {
348+
items: vec![
349+
(0, InnerContext { value: "First value".to_owned() }),
350+
(1, InnerContext { value: "Second value".to_owned() }),
351+
].into_iter().collect(),
352+
};
353+
354+
let (result, errs) = ::execute(doc, None, &schema, &vars, &ctx)
355+
.expect("Execution failed");
356+
357+
assert_eq!(errs, [
358+
ExecutionError::new(
359+
SourcePosition::new(123, 4, 12),
360+
&["tooLarge"],
361+
"Key too large: 200",
362+
),
363+
]);
364+
365+
println!("Result: {:?}", result);
366+
367+
assert_eq!(
368+
result,
369+
Value::object(vec![
370+
("first", Value::object(vec![
371+
("value", Value::string("First value")),
372+
].into_iter().collect())),
373+
("missing", Value::null()),
374+
("tooLarge", Value::null()),
375+
].into_iter().collect()));
376+
}
377+
378+
#[test]
379+
fn test_always() {
380+
let schema = RootNode::new(Schema, EmptyMutation::<OuterContext>::new());
381+
let doc = r"{ first: itemAlways(key: 0) { value } }";
382+
383+
let vars = vec![].into_iter().collect();
384+
385+
let ctx = OuterContext {
386+
items: vec![
387+
(0, InnerContext { value: "First value".to_owned() }),
388+
(1, InnerContext { value: "Second value".to_owned() }),
389+
].into_iter().collect(),
390+
};
391+
392+
let (result, errs) = ::execute(doc, None, &schema, &vars, &ctx)
393+
.expect("Execution failed");
394+
395+
assert_eq!(errs, []);
396+
397+
println!("Result: {:?}", result);
398+
399+
assert_eq!(
400+
result,
401+
Value::object(vec![
402+
("first", Value::object(vec![
403+
("value", Value::string("First value")),
404+
].into_iter().collect())),
405+
].into_iter().collect()));
406+
}
407+
}
408+
198409
mod nulls_out_errors {
199410
use value::Value;
200411
use schema::model::RootNode;

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ pub use value::Value;
221221
pub use types::base::{Arguments, GraphQLType, TypeKind};
222222
pub use executor::{
223223
Context, FromContext,
224-
Executor, Registry, ExecutionResult, ExecutionError, FieldResult, IntoFieldResult,
224+
Executor, Registry, ExecutionResult, ExecutionError, FieldResult, IntoResolvable,
225225
};
226226
pub use validation::RuleError;
227227
pub use types::scalars::{EmptyMutation, ID};

0 commit comments

Comments
 (0)