Updated book for master ***NO_CI***
This commit is contained in:
parent
5c17ae83f1
commit
963a0b4bff
16 changed files with 320 additions and 216 deletions
|
@ -159,10 +159,7 @@ produced by issuing a specially crafted introspection query.</p>
|
||||||
<p>Juniper provides a convenience function to introspect the entire schema. The
|
<p>Juniper provides a convenience function to introspect the entire schema. The
|
||||||
result can then be converted to JSON for use with tools and libraries such as
|
result can then be converted to JSON for use with tools and libraries such as
|
||||||
<a href="https://github.com/graphql-rust/graphql-client">graphql-client</a>:</p>
|
<a href="https://github.com/graphql-rust/graphql-client">graphql-client</a>:</p>
|
||||||
<pre><pre class="playpen"><code class="language-rust"># // Only needed due to 2018 edition because the macro is not accessible.
|
<pre><pre class="playpen"><code class="language-rust">use juniper::{EmptyMutation, FieldResult, IntrospectionFormat};
|
||||||
# extern crate juniper;
|
|
||||||
# extern crate serde_json;
|
|
||||||
use juniper::{EmptyMutation, FieldResult, IntrospectionFormat};
|
|
||||||
|
|
||||||
// Define our schema.
|
// Define our schema.
|
||||||
|
|
||||||
|
@ -176,11 +173,14 @@ impl juniper::Context for Context {}
|
||||||
|
|
||||||
struct Query;
|
struct Query;
|
||||||
|
|
||||||
juniper::graphql_object!(Query: Context |&self| {
|
#[juniper::object(
|
||||||
field example(&executor, id: String) -> FieldResult<Example> {
|
Context = Context,
|
||||||
|
)]
|
||||||
|
impl Query {
|
||||||
|
fn example(id: String) -> FieldResult<Example> {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
type Schema = juniper::RootNode<'static, Query, EmptyMutation<Context>>;
|
type Schema = juniper::RootNode<'static, Query, EmptyMutation<Context>>;
|
||||||
|
|
||||||
|
|
|
@ -157,21 +157,22 @@ enum SignUpResult {
|
||||||
Error(Vec<ValidationError>),
|
Error(Vec<ValidationError>),
|
||||||
}
|
}
|
||||||
|
|
||||||
juniper::graphql_object!(SignUpResult: () |&self| {
|
#[juniper::object]
|
||||||
field user() -> Option<&User> {
|
impl SignUpResult {
|
||||||
|
fn user(&self) -> Option<&User> {
|
||||||
match *self {
|
match *self {
|
||||||
SignUpResult::Ok(ref user) => Some(user),
|
SignUpResult::Ok(ref user) => Some(user),
|
||||||
SignUpResult::Error(_) => None,
|
SignUpResult::Error(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
field error() -> Option<&Vec<ValidationError>> {
|
fn error(&self) -> Option<&Vec<ValidationError>> {
|
||||||
match *self {
|
match *self {
|
||||||
SignUpResult::Ok(_) => None,
|
SignUpResult::Ok(_) => None,
|
||||||
SignUpResult::Error(ref errors) => Some(errors)
|
SignUpResult::Error(ref errors) => Some(errors)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
# fn main() {}
|
# fn main() {}
|
||||||
</code></pre></pre>
|
</code></pre></pre>
|
||||||
|
|
|
@ -158,25 +158,31 @@ struct ValidationError {
|
||||||
# #[allow(dead_code)]
|
# #[allow(dead_code)]
|
||||||
struct MutationResult<T>(Result<T, Vec<ValidationError>>);
|
struct MutationResult<T>(Result<T, Vec<ValidationError>>);
|
||||||
|
|
||||||
juniper::graphql_object!(MutationResult<User>: () as "UserResult" |&self| {
|
#[juniper::object(
|
||||||
field user() -> Option<&User> {
|
name = "UserResult",
|
||||||
|
)]
|
||||||
|
impl MutationResult<User> {
|
||||||
|
fn user(&self) -> Option<&User> {
|
||||||
self.0.as_ref().ok()
|
self.0.as_ref().ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
field error() -> Option<&Vec<ValidationError>> {
|
fn error(&self) -> Option<&Vec<ValidationError>> {
|
||||||
self.0.as_ref().err()
|
self.0.as_ref().err()
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
juniper::graphql_object!(MutationResult<ForumPost>: () as "ForumPostResult" |&self| {
|
#[juniper::object(
|
||||||
field forum_post() -> Option<&ForumPost> {
|
name = "ForumPostResult",
|
||||||
|
)]
|
||||||
|
impl MutationResult<ForumPost> {
|
||||||
|
fn forum_post(&self) -> Option<&ForumPost> {
|
||||||
self.0.as_ref().ok()
|
self.0.as_ref().ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
field error() -> Option<&Vec<ValidationError>> {
|
fn error(&self) -> Option<&Vec<ValidationError>> {
|
||||||
self.0.as_ref().err()
|
self.0.as_ref().err()
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
# fn main() {}
|
# fn main() {}
|
||||||
</code></pre></pre>
|
</code></pre></pre>
|
||||||
|
|
|
@ -194,7 +194,7 @@ naturally map to GraphQL features, such as <code>Option<T></code>, <code>V
|
||||||
<code>String</code>, <code>f64</code>, and <code>i32</code>, references, and slices.</p>
|
<code>String</code>, <code>f64</code>, and <code>i32</code>, references, and slices.</p>
|
||||||
<p>For more advanced mappings, Juniper provides multiple macros to map your Rust
|
<p>For more advanced mappings, Juniper provides multiple macros to map your Rust
|
||||||
types to a GraphQL schema. The most important one is the
|
types to a GraphQL schema. The most important one is the
|
||||||
<a href="https://docs.rs/juniper/latest/juniper/macro.graphql_object.html">graphql_object!</a> macro that is used for declaring an object with
|
[object][jp_object] procedural macro that is used for declaring an object with
|
||||||
resolvers, which you will use for the <code>Query</code> and <code>Mutation</code> roots.</p>
|
resolvers, which you will use for the <code>Query</code> and <code>Mutation</code> roots.</p>
|
||||||
<pre><pre class="playpen"><code class="language-rust">use juniper::{FieldResult};
|
<pre><pre class="playpen"><code class="language-rust">use juniper::{FieldResult};
|
||||||
|
|
||||||
|
@ -232,7 +232,7 @@ struct NewHuman {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now, we create our root Query and Mutation types with resolvers by using the
|
// Now, we create our root Query and Mutation types with resolvers by using the
|
||||||
// graphql_object! macro.
|
// object macro.
|
||||||
// Objects can have contexts that allow accessing shared state like a database
|
// Objects can have contexts that allow accessing shared state like a database
|
||||||
// pool.
|
// pool.
|
||||||
|
|
||||||
|
@ -246,17 +246,23 @@ impl juniper::Context for Context {}
|
||||||
|
|
||||||
struct Query;
|
struct Query;
|
||||||
|
|
||||||
juniper::graphql_object!(Query: Context |&self| {
|
#[juniper::object(
|
||||||
|
// Here we specify the context type for the object.
|
||||||
|
// We need to do this in every type that
|
||||||
|
// needs access to the context.
|
||||||
|
Context = Context,
|
||||||
|
)]
|
||||||
|
impl Query {
|
||||||
|
|
||||||
field apiVersion() -> &str {
|
fn apiVersion() -> &str {
|
||||||
"1.0"
|
"1.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Arguments to resolvers can either be simple types or input objects.
|
// Arguments to resolvers can either be simple types or input objects.
|
||||||
// The executor is a special (optional) argument that allows accessing the context.
|
// To gain access to the context, we specify a argument
|
||||||
field human(&executor, id: String) -> FieldResult<Human> {
|
// that is a reference to the Context type.
|
||||||
// Get the context from the executor.
|
// Juniper automatically injects the correct context here.
|
||||||
let context = executor.context();
|
fn human(context: &Context, id: String) -> FieldResult<Human> {
|
||||||
// Get a db connection.
|
// Get a db connection.
|
||||||
let connection = context.pool.get_connection()?;
|
let connection = context.pool.get_connection()?;
|
||||||
// Execute a db query.
|
// Execute a db query.
|
||||||
|
@ -265,18 +271,23 @@ juniper::graphql_object!(Query: Context |&self| {
|
||||||
// Return the result.
|
// Return the result.
|
||||||
Ok(human)
|
Ok(human)
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
// Now, we do the same for our Mutation type.
|
||||||
|
|
||||||
struct Mutation;
|
struct Mutation;
|
||||||
|
|
||||||
juniper::graphql_object!(Mutation: Context |&self| {
|
#[juniper::object(
|
||||||
|
Context = Context,
|
||||||
|
)]
|
||||||
|
impl Mutation {
|
||||||
|
|
||||||
field createHuman(&executor, new_human: NewHuman) -> FieldResult<Human> {
|
fn createHuman(context: &Context, new_human: NewHuman) -> FieldResult<Human> {
|
||||||
let db = executor.context().pool.get_connection()?;
|
let db = executor.context().pool.get_connection()?;
|
||||||
let human: Human = db.insert_human(&new_human)?;
|
let human: Human = db.insert_human(&new_human)?;
|
||||||
Ok(human)
|
Ok(human)
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
// A root schema consists of a query and a mutation.
|
// A root schema consists of a query and a mutation.
|
||||||
// Request queries can be executed against a RootNode.
|
// Request queries can be executed against a RootNode.
|
||||||
|
@ -295,6 +306,7 @@ type Schema = juniper::RootNode<'static, Query, Mutation>;
|
||||||
# #[macro_use] extern crate juniper;
|
# #[macro_use] extern crate juniper;
|
||||||
use juniper::{FieldResult, Variables, EmptyMutation};
|
use juniper::{FieldResult, Variables, EmptyMutation};
|
||||||
|
|
||||||
|
|
||||||
#[derive(juniper::GraphQLEnum, Clone, Copy)]
|
#[derive(juniper::GraphQLEnum, Clone, Copy)]
|
||||||
enum Episode {
|
enum Episode {
|
||||||
NewHope,
|
NewHope,
|
||||||
|
@ -302,18 +314,23 @@ enum Episode {
|
||||||
Jedi,
|
Jedi,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Query;
|
|
||||||
|
|
||||||
juniper::graphql_object!(Query: Ctx |&self| {
|
|
||||||
field favoriteEpisode(&executor) -> FieldResult<Episode> {
|
|
||||||
// Use the special &executor argument to fetch our fav episode.
|
|
||||||
Ok(executor.context().0)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Arbitrary context data.
|
// Arbitrary context data.
|
||||||
struct Ctx(Episode);
|
struct Ctx(Episode);
|
||||||
|
|
||||||
|
impl juniper::Context for Ctx {}
|
||||||
|
|
||||||
|
struct Query;
|
||||||
|
|
||||||
|
#[juniper::object(
|
||||||
|
Context = Ctx,
|
||||||
|
)]
|
||||||
|
impl Query {
|
||||||
|
fn favoriteEpisode(context: &Ctx) -> FieldResult<Episode> {
|
||||||
|
Ok(context.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// A root schema consists of a query and a mutation.
|
// A root schema consists of a query and a mutation.
|
||||||
// Request queries can be executed against a RootNode.
|
// Request queries can be executed against a RootNode.
|
||||||
type Schema = juniper::RootNode<'static, Query, EmptyMutation<Ctx>>;
|
type Schema = juniper::RootNode<'static, Query, EmptyMutation<Ctx>>;
|
||||||
|
@ -491,7 +508,7 @@ attribute:</p>
|
||||||
struct Person {
|
struct Person {
|
||||||
name: String,
|
name: String,
|
||||||
age: i32,
|
age: i32,
|
||||||
#[graphql(deprecation="Please use the name field instead")]
|
#[graphql(deprecated = "Please use the name field instead")]
|
||||||
first_name: String,
|
first_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -516,8 +533,8 @@ struct Person {
|
||||||
<a class="header" href="#complex-fields" id="complex-fields"><h1>Complex fields</h1></a>
|
<a class="header" href="#complex-fields" id="complex-fields"><h1>Complex fields</h1></a>
|
||||||
<p>If you've got a struct that can't be mapped directly to GraphQL, that contains
|
<p>If you've got a struct that can't be mapped directly to GraphQL, that contains
|
||||||
computed fields or circular structures, you have to use a more powerful tool:
|
computed fields or circular structures, you have to use a more powerful tool:
|
||||||
the <code>graphql_object!</code> macro. This macro lets you define GraphQL objects similar
|
the <code>object</code> procedural macro. This macro lets you define GraphQL object
|
||||||
to how you define methods in a Rust <code>impl</code> block for a type. Continuing with the
|
fields in a Rust <code>impl</code> block for a type. Continuing with the
|
||||||
example from the last chapter, this is how you would define <code>Person</code> using the
|
example from the last chapter, this is how you would define <code>Person</code> using the
|
||||||
macro:</p>
|
macro:</p>
|
||||||
<pre><pre class="playpen"><code class="language-rust">
|
<pre><pre class="playpen"><code class="language-rust">
|
||||||
|
@ -526,15 +543,16 @@ struct Person {
|
||||||
age: i32,
|
age: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
juniper::graphql_object!(Person: () |&self| {
|
#[juniper::object]
|
||||||
field name() -> &str {
|
impl Person {
|
||||||
|
fn name(&self) -> &str {
|
||||||
self.name.as_str()
|
self.name.as_str()
|
||||||
}
|
}
|
||||||
|
|
||||||
field age() -> i32 {
|
fn age(&self) -> i32 {
|
||||||
self.age
|
self.age
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
# fn main() { }
|
# fn main() { }
|
||||||
</code></pre></pre>
|
</code></pre></pre>
|
||||||
|
@ -550,12 +568,13 @@ struct House {
|
||||||
inhabitants: Vec<Person>,
|
inhabitants: Vec<Person>,
|
||||||
}
|
}
|
||||||
|
|
||||||
juniper::graphql_object!(House: () |&self| {
|
#[juniper::object]
|
||||||
|
impl House {
|
||||||
// Creates the field inhabitantWithName(name), returning a nullable person
|
// Creates the field inhabitantWithName(name), returning a nullable person
|
||||||
field inhabitant_with_name(name: String) -> Option<&Person> {
|
fn inhabitant_with_name(&self, name: String) -> Option<&Person> {
|
||||||
self.inhabitants.iter().find(|p| p.name == name)
|
self.inhabitants.iter().find(|p| p.name == name)
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
# fn main() {}
|
# fn main() {}
|
||||||
</code></pre></pre>
|
</code></pre></pre>
|
||||||
|
@ -571,15 +590,19 @@ the field. Also, the type name can be changed with an alias:</p>
|
||||||
website_url: String,
|
website_url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
juniper::graphql_object!(Person: () as "PersonObject" |&self| {
|
#[juniper::object(
|
||||||
field name() -> &str {
|
// With this attribtue you can change the public GraphQL name of the type.
|
||||||
|
name = "PersonObject",
|
||||||
|
)]
|
||||||
|
impl Person {
|
||||||
|
fn name(&self) -> &str {
|
||||||
self.name.as_str()
|
self.name.as_str()
|
||||||
}
|
}
|
||||||
|
|
||||||
field websiteURL() -> &str {
|
fn websiteURL(&self) -> &str {
|
||||||
self.website_url.as_str()
|
self.website_url.as_str()
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
# fn main() { }
|
# fn main() { }
|
||||||
</code></pre></pre>
|
</code></pre></pre>
|
||||||
|
@ -590,7 +613,7 @@ juniper::graphql_object!(Person: () as "PersonObject" |&self| {
|
||||||
<li>Per-argument default values</li>
|
<li>Per-argument default values</li>
|
||||||
<li>Per-argument descriptions</li>
|
<li>Per-argument descriptions</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>These, and more features, are described more thorougly in <a href="https://docs.rs/juniper/0.8.1/juniper/macro.graphql_object.html">the reference
|
<p>These, and more features, are described more thorougly in <a href="https://docs.rs/juniper/latest/juniper/macro.object.html">the reference
|
||||||
documentation</a>.</p>
|
documentation</a>.</p>
|
||||||
<a class="header" href="#using-contexts" id="using-contexts"><h1>Using contexts</h1></a>
|
<a class="header" href="#using-contexts" id="using-contexts"><h1>Using contexts</h1></a>
|
||||||
<p>The context type is a feature in Juniper that lets field resolvers access global
|
<p>The context type is a feature in Juniper that lets field resolvers access global
|
||||||
|
@ -619,40 +642,53 @@ struct User {
|
||||||
<p>We would like a <code>friends</code> field on <code>User</code> that returns a list of <code>User</code> objects.
|
<p>We would like a <code>friends</code> field on <code>User</code> that returns a list of <code>User</code> objects.
|
||||||
In order to write such a field though, the database must be queried.</p>
|
In order to write such a field though, the database must be queried.</p>
|
||||||
<p>To solve this, we mark the <code>Database</code> as a valid context type and assign it to
|
<p>To solve this, we mark the <code>Database</code> as a valid context type and assign it to
|
||||||
the user object. Then, we use the special <code>&executor</code> argument to access the
|
the user object.</p>
|
||||||
current context object:</p>
|
<p>To gain access to the context, we need to specify an argument with the same
|
||||||
|
type as the specified <code>Context</code> for the type:</p>
|
||||||
<pre><pre class="playpen"><code class="language-rust"># use std::collections::HashMap;
|
<pre><pre class="playpen"><code class="language-rust"># use std::collections::HashMap;
|
||||||
extern crate juniper;
|
extern crate juniper;
|
||||||
|
|
||||||
|
// This struct represents our context.
|
||||||
struct Database {
|
struct Database {
|
||||||
users: HashMap<i32, User>,
|
users: HashMap<i32, User>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mark the Database as a valid context type for Juniper
|
||||||
|
impl juniper::Context for Database {}
|
||||||
|
|
||||||
struct User {
|
struct User {
|
||||||
id: i32,
|
id: i32,
|
||||||
name: String,
|
name: String,
|
||||||
friend_ids: Vec<i32>,
|
friend_ids: Vec<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. Mark the Database as a valid context type for Juniper
|
|
||||||
impl juniper::Context for Database {}
|
|
||||||
|
|
||||||
// 2. Assign Database as the context type for User
|
// Assign Database as the context type for User
|
||||||
juniper::graphql_object!(User: Database |&self| {
|
#[juniper::object(
|
||||||
// 3. Use the special executor argument
|
Context = Database,
|
||||||
field friends(&executor) -> Vec<&User> {
|
)]
|
||||||
// 4. Use the executor to access the context object
|
impl User {
|
||||||
let database = executor.context();
|
// 3. Inject the context by specifying an argument
|
||||||
|
// with the context type.
|
||||||
|
// Note:
|
||||||
|
// - the type must be a reference
|
||||||
|
// - the name of the argument SHOULD be context
|
||||||
|
fn friends(&self, context: &Database) -> Vec<&User> {
|
||||||
|
|
||||||
// 5. Use the database to lookup users
|
// 5. Use the database to lookup users
|
||||||
self.friend_ids.iter()
|
self.friend_ids.iter()
|
||||||
.map(|id| database.users.get(id).expect("Could not find user with ID"))
|
.map(|id| context.users.get(id).expect("Could not find user with ID"))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
field name() -> &str { self.name.as_str() }
|
fn name(&self) -> &str {
|
||||||
field id() -> i32 { self.id }
|
self.name.as_str()
|
||||||
});
|
}
|
||||||
|
|
||||||
|
fn id(&self) -> i32 {
|
||||||
|
self.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# fn main() { }
|
# fn main() { }
|
||||||
</code></pre></pre>
|
</code></pre></pre>
|
||||||
|
@ -683,14 +719,16 @@ struct Example {
|
||||||
filename: PathBuf,
|
filename: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
juniper::graphql_object!(Example: () |&self| {
|
#[juniper::object]
|
||||||
field contents() -> FieldResult<String> {
|
impl Example {
|
||||||
|
fn contents() -> FieldResult<String> {
|
||||||
let mut file = File::open(&self.filename)?;
|
let mut file = File::open(&self.filename)?;
|
||||||
let mut contents = String::new();
|
let mut contents = String::new();
|
||||||
file.read_to_string(&mut contents)?;
|
file.read_to_string(&mut contents)?;
|
||||||
Ok(contents)
|
Ok(contents)
|
||||||
}
|
}
|
||||||
field foo() -> FieldResult<Option<String>> {
|
|
||||||
|
fn foo() -> FieldResult<Option<String>> {
|
||||||
// Some invalid bytes.
|
// Some invalid bytes.
|
||||||
let invalid = vec![128, 223];
|
let invalid = vec![128, 223];
|
||||||
|
|
||||||
|
@ -699,7 +737,7 @@ juniper::graphql_object!(Example: () |&self| {
|
||||||
Err(e) => Err(e)?,
|
Err(e) => Err(e)?,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
# fn main() {}
|
# fn main() {}
|
||||||
</code></pre></pre>
|
</code></pre></pre>
|
||||||
|
@ -779,14 +817,15 @@ struct Example {
|
||||||
whatever: Option<bool>,
|
whatever: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
juniper::graphql_object!(Example: () |&self| {
|
#[juniper::object]
|
||||||
field whatever() -> Result<bool, CustomError> {
|
impl Example {
|
||||||
|
fn whatever() -> Result<bool, CustomError> {
|
||||||
if let Some(value) = self.whatever {
|
if let Some(value) = self.whatever {
|
||||||
return Ok(value);
|
return Ok(value);
|
||||||
}
|
}
|
||||||
Err(CustomError::WhateverNotSet)
|
Err(CustomError::WhateverNotSet)
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
# fn main() {}
|
# fn main() {}
|
||||||
</code></pre></pre>
|
</code></pre></pre>
|
||||||
|
@ -925,14 +964,14 @@ you can do away with the downcast methods and use the context instead. Here,
|
||||||
we'll use two hashmaps, but this could be two tables and some SQL calls instead:</p>
|
we'll use two hashmaps, but this could be two tables and some SQL calls instead:</p>
|
||||||
<pre><pre class="playpen"><code class="language-rust"># use std::collections::HashMap;
|
<pre><pre class="playpen"><code class="language-rust"># use std::collections::HashMap;
|
||||||
#[derive(juniper::GraphQLObject)]
|
#[derive(juniper::GraphQLObject)]
|
||||||
#[graphql(Context = "Database")]
|
#[graphql(Context = Database)]
|
||||||
struct Human {
|
struct Human {
|
||||||
id: String,
|
id: String,
|
||||||
home_planet: String,
|
home_planet: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(juniper::GraphQLObject)]
|
#[derive(juniper::GraphQLObject)]
|
||||||
#[graphql(Context = "Database")]
|
#[graphql(Context = Database)]
|
||||||
struct Droid {
|
struct Droid {
|
||||||
id: String,
|
id: String,
|
||||||
primary_function: String,
|
primary_function: String,
|
||||||
|
@ -1062,12 +1101,14 @@ struct Coordinate {
|
||||||
struct Root;
|
struct Root;
|
||||||
# #[derive(juniper::GraphQLObject)] struct User { name: String }
|
# #[derive(juniper::GraphQLObject)] struct User { name: String }
|
||||||
|
|
||||||
juniper::graphql_object!(Root: () |&self| {
|
#[juniper::object]
|
||||||
field users_at_location(coordinate: Coordinate, radius: f64) -> Vec<User> {
|
impl Root {
|
||||||
|
fn users_at_location(coordinate: Coordinate, radius: f64) -> Vec<User> {
|
||||||
// Send coordinate to database
|
// Send coordinate to database
|
||||||
|
// ...
|
||||||
# unimplemented!()
|
# unimplemented!()
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
# fn main() {}
|
# fn main() {}
|
||||||
</code></pre></pre>
|
</code></pre></pre>
|
||||||
|
@ -1087,12 +1128,14 @@ struct WorldCoordinate {
|
||||||
struct Root;
|
struct Root;
|
||||||
# #[derive(juniper::GraphQLObject)] struct User { name: String }
|
# #[derive(juniper::GraphQLObject)] struct User { name: String }
|
||||||
|
|
||||||
juniper::graphql_object!(Root: () |&self| {
|
#[juniper::object]
|
||||||
field users_at_location(coordinate: WorldCoordinate, radius: f64) -> Vec<User> {
|
impl Root {
|
||||||
|
fn users_at_location(coordinate: WorldCoordinate, radius: f64) -> Vec<User> {
|
||||||
// Send coordinate to database
|
// Send coordinate to database
|
||||||
|
// ...
|
||||||
# unimplemented!()
|
# unimplemented!()
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
# fn main() {}
|
# fn main() {}
|
||||||
</code></pre></pre>
|
</code></pre></pre>
|
||||||
|
@ -1198,14 +1241,14 @@ juniper::graphql_union!(<'a> &'a Character: () as "Character"
|
||||||
<p>FIXME: This example does not compile at the moment</p>
|
<p>FIXME: This example does not compile at the moment</p>
|
||||||
<pre><pre class="playpen"><code class="language-rust"># use std::collections::HashMap;
|
<pre><pre class="playpen"><code class="language-rust"># use std::collections::HashMap;
|
||||||
#[derive(juniper::GraphQLObject)]
|
#[derive(juniper::GraphQLObject)]
|
||||||
#[graphql(Context = "Database")]
|
#[graphql(Context = Database)]
|
||||||
struct Human {
|
struct Human {
|
||||||
id: String,
|
id: String,
|
||||||
home_planet: String,
|
home_planet: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(juniper::GraphQLObject)]
|
#[derive(juniper::GraphQLObject)]
|
||||||
#[graphql(Context = "Database")]
|
#[graphql(Context = Database)]
|
||||||
struct Droid {
|
struct Droid {
|
||||||
id: String,
|
id: String,
|
||||||
primary_function: String,
|
primary_function: String,
|
||||||
|
@ -1242,14 +1285,14 @@ juniper::graphql_union!(<'a> &'a Character: Database as "Characte
|
||||||
<a class="header" href="#placeholder-objects-1" id="placeholder-objects-1"><h2>Placeholder objects</h2></a>
|
<a class="header" href="#placeholder-objects-1" id="placeholder-objects-1"><h2>Placeholder objects</h2></a>
|
||||||
<pre><pre class="playpen"><code class="language-rust"># use std::collections::HashMap;
|
<pre><pre class="playpen"><code class="language-rust"># use std::collections::HashMap;
|
||||||
#[derive(juniper::GraphQLObject)]
|
#[derive(juniper::GraphQLObject)]
|
||||||
#[graphql(Context = "Database")]
|
#[graphql(Context = Database)]
|
||||||
struct Human {
|
struct Human {
|
||||||
id: String,
|
id: String,
|
||||||
home_planet: String,
|
home_planet: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(juniper::GraphQLObject)]
|
#[derive(juniper::GraphQLObject)]
|
||||||
#[graphql(Context = "Database")]
|
#[graphql(Context = Database)]
|
||||||
struct Droid {
|
struct Droid {
|
||||||
id: String,
|
id: String,
|
||||||
primary_function: String,
|
primary_function: String,
|
||||||
|
@ -1319,17 +1362,18 @@ and register all types it can find. This means that if you define a GraphQL
|
||||||
object somewhere but never references it, it will not be exposed in a schema.</p>
|
object somewhere but never references it, it will not be exposed in a schema.</p>
|
||||||
<a class="header" href="#the-query-root" id="the-query-root"><h2>The query root</h2></a>
|
<a class="header" href="#the-query-root" id="the-query-root"><h2>The query root</h2></a>
|
||||||
<p>The query root is just a GraphQL object. You define it like any other GraphQL
|
<p>The query root is just a GraphQL object. You define it like any other GraphQL
|
||||||
object in Juniper, most commonly using the <code>graphql_object!</code> macro:</p>
|
object in Juniper, most commonly using the <code>object</code> proc macro:</p>
|
||||||
<pre><pre class="playpen"><code class="language-rust"># use juniper::FieldResult;
|
<pre><pre class="playpen"><code class="language-rust"># use juniper::FieldResult;
|
||||||
# #[derive(juniper::GraphQLObject)] struct User { name: String }
|
# #[derive(juniper::GraphQLObject)] struct User { name: String }
|
||||||
struct Root;
|
struct Root;
|
||||||
|
|
||||||
juniper::graphql_object!(Root: () |&self| {
|
#[juniper::object]
|
||||||
field userWithUsername(username: String) -> FieldResult<Option<User>> {
|
impl Root {
|
||||||
|
fn userWithUsername(username: String) -> FieldResult<Option<User>> {
|
||||||
// Look up user in database...
|
// Look up user in database...
|
||||||
# unimplemented!()
|
# unimplemented!()
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
# fn main() { }
|
# fn main() { }
|
||||||
</code></pre></pre>
|
</code></pre></pre>
|
||||||
|
@ -1340,12 +1384,13 @@ usually performs some mutating side-effect, such as updating a database.</p>
|
||||||
# #[derive(juniper::GraphQLObject)] struct User { name: String }
|
# #[derive(juniper::GraphQLObject)] struct User { name: String }
|
||||||
struct Mutations;
|
struct Mutations;
|
||||||
|
|
||||||
juniper::graphql_object!(Mutations: () |&self| {
|
#[juniper::object]
|
||||||
field signUpUser(name: String, email: String) -> FieldResult<User> {
|
impl Mutations {
|
||||||
|
fn signUpUser(name: String, email: String) -> FieldResult<User> {
|
||||||
// Validate inputs and save user in database...
|
// Validate inputs and save user in database...
|
||||||
# unimplemented!()
|
# unimplemented!()
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
# fn main() { }
|
# fn main() { }
|
||||||
</code></pre></pre>
|
</code></pre></pre>
|
||||||
|
@ -1440,11 +1485,12 @@ fn context_factory(_: &mut Request) -> IronResult<()> {
|
||||||
|
|
||||||
struct Root;
|
struct Root;
|
||||||
|
|
||||||
juniper::graphql_object!(Root: () |&self| {
|
#[juniper::object]
|
||||||
field foo() -> String {
|
impl Root {
|
||||||
|
fn foo() -> String {
|
||||||
"Bar".to_owned()
|
"Bar".to_owned()
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
# #[allow(unreachable_code, unused_variables)]
|
# #[allow(unreachable_code, unused_variables)]
|
||||||
fn main() {
|
fn main() {
|
||||||
|
@ -1487,13 +1533,14 @@ fn context_factory(req: &mut Request) -> IronResult<Context> {
|
||||||
|
|
||||||
struct Root;
|
struct Root;
|
||||||
|
|
||||||
juniper::graphql_object!(Root: Context |&self| {
|
#[juniper::object(
|
||||||
field my_addr(&executor) -> String {
|
Context = Context,
|
||||||
let context = executor.context();
|
)]
|
||||||
|
impl Root {
|
||||||
|
field my_addr(context: &Context) -> String {
|
||||||
format!("Hello, you're coming from {}", context.remote_addr)
|
format!("Hello, you're coming from {}", context.remote_addr)
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
# fn main() {
|
# fn main() {
|
||||||
# let _graphql_endpoint = juniper_iron::GraphQLHandler::new(
|
# let _graphql_endpoint = juniper_iron::GraphQLHandler::new(
|
||||||
|
@ -1503,8 +1550,6 @@ juniper::graphql_object!(Root: Context |&self| {
|
||||||
# );
|
# );
|
||||||
# }
|
# }
|
||||||
</code></pre>
|
</code></pre>
|
||||||
<a class="header" href="#accessing-global-data" id="accessing-global-data"><h2>Accessing global data</h2></a>
|
|
||||||
<p>FIXME: Show how the <code>persistent</code> crate works with contexts using e.g. <code>r2d2</code>.</p>
|
|
||||||
<a class="header" href="#integrating-with-hyper" id="integrating-with-hyper"><h1>Integrating with Hyper</h1></a>
|
<a class="header" href="#integrating-with-hyper" id="integrating-with-hyper"><h1>Integrating with Hyper</h1></a>
|
||||||
<p><a href="https://hyper.rs/">Hyper</a> is a is a fast HTTP implementation that many other Rust web frameworks
|
<p><a href="https://hyper.rs/">Hyper</a> is a is a fast HTTP implementation that many other Rust web frameworks
|
||||||
leverage. It offers asynchronous I/O via the tokio runtime and works on
|
leverage. It offers asynchronous I/O via the tokio runtime and works on
|
||||||
|
@ -1560,10 +1605,7 @@ produced by issuing a specially crafted introspection query.</p>
|
||||||
<p>Juniper provides a convenience function to introspect the entire schema. The
|
<p>Juniper provides a convenience function to introspect the entire schema. The
|
||||||
result can then be converted to JSON for use with tools and libraries such as
|
result can then be converted to JSON for use with tools and libraries such as
|
||||||
<a href="https://github.com/graphql-rust/graphql-client">graphql-client</a>:</p>
|
<a href="https://github.com/graphql-rust/graphql-client">graphql-client</a>:</p>
|
||||||
<pre><pre class="playpen"><code class="language-rust"># // Only needed due to 2018 edition because the macro is not accessible.
|
<pre><pre class="playpen"><code class="language-rust">use juniper::{EmptyMutation, FieldResult, IntrospectionFormat};
|
||||||
# extern crate juniper;
|
|
||||||
# extern crate serde_json;
|
|
||||||
use juniper::{EmptyMutation, FieldResult, IntrospectionFormat};
|
|
||||||
|
|
||||||
// Define our schema.
|
// Define our schema.
|
||||||
|
|
||||||
|
@ -1577,11 +1619,14 @@ impl juniper::Context for Context {}
|
||||||
|
|
||||||
struct Query;
|
struct Query;
|
||||||
|
|
||||||
juniper::graphql_object!(Query: Context |&self| {
|
#[juniper::object(
|
||||||
field example(&executor, id: String) -> FieldResult<Example> {
|
Context = Context,
|
||||||
|
)]
|
||||||
|
impl Query {
|
||||||
|
fn example(id: String) -> FieldResult<Example> {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
type Schema = juniper::RootNode<'static, Query, EmptyMutation<Context>>;
|
type Schema = juniper::RootNode<'static, Query, EmptyMutation<Context>>;
|
||||||
|
|
||||||
|
@ -1622,21 +1667,22 @@ enum SignUpResult {
|
||||||
Error(Vec<ValidationError>),
|
Error(Vec<ValidationError>),
|
||||||
}
|
}
|
||||||
|
|
||||||
juniper::graphql_object!(SignUpResult: () |&self| {
|
#[juniper::object]
|
||||||
field user() -> Option<&User> {
|
impl SignUpResult {
|
||||||
|
fn user(&self) -> Option<&User> {
|
||||||
match *self {
|
match *self {
|
||||||
SignUpResult::Ok(ref user) => Some(user),
|
SignUpResult::Ok(ref user) => Some(user),
|
||||||
SignUpResult::Error(_) => None,
|
SignUpResult::Error(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
field error() -> Option<&Vec<ValidationError>> {
|
fn error(&self) -> Option<&Vec<ValidationError>> {
|
||||||
match *self {
|
match *self {
|
||||||
SignUpResult::Ok(_) => None,
|
SignUpResult::Ok(_) => None,
|
||||||
SignUpResult::Error(ref errors) => Some(errors)
|
SignUpResult::Error(ref errors) => Some(errors)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
# fn main() {}
|
# fn main() {}
|
||||||
</code></pre></pre>
|
</code></pre></pre>
|
||||||
|
@ -1672,25 +1718,31 @@ struct ValidationError {
|
||||||
# #[allow(dead_code)]
|
# #[allow(dead_code)]
|
||||||
struct MutationResult<T>(Result<T, Vec<ValidationError>>);
|
struct MutationResult<T>(Result<T, Vec<ValidationError>>);
|
||||||
|
|
||||||
juniper::graphql_object!(MutationResult<User>: () as "UserResult" |&self| {
|
#[juniper::object(
|
||||||
field user() -> Option<&User> {
|
name = "UserResult",
|
||||||
|
)]
|
||||||
|
impl MutationResult<User> {
|
||||||
|
fn user(&self) -> Option<&User> {
|
||||||
self.0.as_ref().ok()
|
self.0.as_ref().ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
field error() -> Option<&Vec<ValidationError>> {
|
fn error(&self) -> Option<&Vec<ValidationError>> {
|
||||||
self.0.as_ref().err()
|
self.0.as_ref().err()
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
juniper::graphql_object!(MutationResult<ForumPost>: () as "ForumPostResult" |&self| {
|
#[juniper::object(
|
||||||
field forum_post() -> Option<&ForumPost> {
|
name = "ForumPostResult",
|
||||||
|
)]
|
||||||
|
impl MutationResult<ForumPost> {
|
||||||
|
fn forum_post(&self) -> Option<&ForumPost> {
|
||||||
self.0.as_ref().ok()
|
self.0.as_ref().ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
field error() -> Option<&Vec<ValidationError>> {
|
fn error(&self) -> Option<&Vec<ValidationError>> {
|
||||||
self.0.as_ref().err()
|
self.0.as_ref().err()
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
# fn main() {}
|
# fn main() {}
|
||||||
</code></pre></pre>
|
</code></pre></pre>
|
||||||
|
|
|
@ -150,7 +150,7 @@ naturally map to GraphQL features, such as <code>Option<T></code>, <code>V
|
||||||
<code>String</code>, <code>f64</code>, and <code>i32</code>, references, and slices.</p>
|
<code>String</code>, <code>f64</code>, and <code>i32</code>, references, and slices.</p>
|
||||||
<p>For more advanced mappings, Juniper provides multiple macros to map your Rust
|
<p>For more advanced mappings, Juniper provides multiple macros to map your Rust
|
||||||
types to a GraphQL schema. The most important one is the
|
types to a GraphQL schema. The most important one is the
|
||||||
<a href="https://docs.rs/juniper/latest/juniper/macro.graphql_object.html">graphql_object!</a> macro that is used for declaring an object with
|
[object][jp_object] procedural macro that is used for declaring an object with
|
||||||
resolvers, which you will use for the <code>Query</code> and <code>Mutation</code> roots.</p>
|
resolvers, which you will use for the <code>Query</code> and <code>Mutation</code> roots.</p>
|
||||||
<pre><pre class="playpen"><code class="language-rust">use juniper::{FieldResult};
|
<pre><pre class="playpen"><code class="language-rust">use juniper::{FieldResult};
|
||||||
|
|
||||||
|
@ -188,7 +188,7 @@ struct NewHuman {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now, we create our root Query and Mutation types with resolvers by using the
|
// Now, we create our root Query and Mutation types with resolvers by using the
|
||||||
// graphql_object! macro.
|
// object macro.
|
||||||
// Objects can have contexts that allow accessing shared state like a database
|
// Objects can have contexts that allow accessing shared state like a database
|
||||||
// pool.
|
// pool.
|
||||||
|
|
||||||
|
@ -202,17 +202,23 @@ impl juniper::Context for Context {}
|
||||||
|
|
||||||
struct Query;
|
struct Query;
|
||||||
|
|
||||||
juniper::graphql_object!(Query: Context |&self| {
|
#[juniper::object(
|
||||||
|
// Here we specify the context type for the object.
|
||||||
|
// We need to do this in every type that
|
||||||
|
// needs access to the context.
|
||||||
|
Context = Context,
|
||||||
|
)]
|
||||||
|
impl Query {
|
||||||
|
|
||||||
field apiVersion() -> &str {
|
fn apiVersion() -> &str {
|
||||||
"1.0"
|
"1.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Arguments to resolvers can either be simple types or input objects.
|
// Arguments to resolvers can either be simple types or input objects.
|
||||||
// The executor is a special (optional) argument that allows accessing the context.
|
// To gain access to the context, we specify a argument
|
||||||
field human(&executor, id: String) -> FieldResult<Human> {
|
// that is a reference to the Context type.
|
||||||
// Get the context from the executor.
|
// Juniper automatically injects the correct context here.
|
||||||
let context = executor.context();
|
fn human(context: &Context, id: String) -> FieldResult<Human> {
|
||||||
// Get a db connection.
|
// Get a db connection.
|
||||||
let connection = context.pool.get_connection()?;
|
let connection = context.pool.get_connection()?;
|
||||||
// Execute a db query.
|
// Execute a db query.
|
||||||
|
@ -221,18 +227,23 @@ juniper::graphql_object!(Query: Context |&self| {
|
||||||
// Return the result.
|
// Return the result.
|
||||||
Ok(human)
|
Ok(human)
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
// Now, we do the same for our Mutation type.
|
||||||
|
|
||||||
struct Mutation;
|
struct Mutation;
|
||||||
|
|
||||||
juniper::graphql_object!(Mutation: Context |&self| {
|
#[juniper::object(
|
||||||
|
Context = Context,
|
||||||
|
)]
|
||||||
|
impl Mutation {
|
||||||
|
|
||||||
field createHuman(&executor, new_human: NewHuman) -> FieldResult<Human> {
|
fn createHuman(context: &Context, new_human: NewHuman) -> FieldResult<Human> {
|
||||||
let db = executor.context().pool.get_connection()?;
|
let db = executor.context().pool.get_connection()?;
|
||||||
let human: Human = db.insert_human(&new_human)?;
|
let human: Human = db.insert_human(&new_human)?;
|
||||||
Ok(human)
|
Ok(human)
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
// A root schema consists of a query and a mutation.
|
// A root schema consists of a query and a mutation.
|
||||||
// Request queries can be executed against a RootNode.
|
// Request queries can be executed against a RootNode.
|
||||||
|
@ -251,6 +262,7 @@ type Schema = juniper::RootNode<'static, Query, Mutation>;
|
||||||
# #[macro_use] extern crate juniper;
|
# #[macro_use] extern crate juniper;
|
||||||
use juniper::{FieldResult, Variables, EmptyMutation};
|
use juniper::{FieldResult, Variables, EmptyMutation};
|
||||||
|
|
||||||
|
|
||||||
#[derive(juniper::GraphQLEnum, Clone, Copy)]
|
#[derive(juniper::GraphQLEnum, Clone, Copy)]
|
||||||
enum Episode {
|
enum Episode {
|
||||||
NewHope,
|
NewHope,
|
||||||
|
@ -258,18 +270,23 @@ enum Episode {
|
||||||
Jedi,
|
Jedi,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Query;
|
|
||||||
|
|
||||||
juniper::graphql_object!(Query: Ctx |&self| {
|
|
||||||
field favoriteEpisode(&executor) -> FieldResult<Episode> {
|
|
||||||
// Use the special &executor argument to fetch our fav episode.
|
|
||||||
Ok(executor.context().0)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Arbitrary context data.
|
// Arbitrary context data.
|
||||||
struct Ctx(Episode);
|
struct Ctx(Episode);
|
||||||
|
|
||||||
|
impl juniper::Context for Ctx {}
|
||||||
|
|
||||||
|
struct Query;
|
||||||
|
|
||||||
|
#[juniper::object(
|
||||||
|
Context = Ctx,
|
||||||
|
)]
|
||||||
|
impl Query {
|
||||||
|
fn favoriteEpisode(context: &Ctx) -> FieldResult<Episode> {
|
||||||
|
Ok(context.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// A root schema consists of a query and a mutation.
|
// A root schema consists of a query and a mutation.
|
||||||
// Request queries can be executed against a RootNode.
|
// Request queries can be executed against a RootNode.
|
||||||
type Schema = juniper::RootNode<'static, Query, EmptyMutation<Ctx>>;
|
type Schema = juniper::RootNode<'static, Query, EmptyMutation<Ctx>>;
|
||||||
|
|
|
@ -152,17 +152,18 @@ and register all types it can find. This means that if you define a GraphQL
|
||||||
object somewhere but never references it, it will not be exposed in a schema.</p>
|
object somewhere but never references it, it will not be exposed in a schema.</p>
|
||||||
<a class="header" href="#the-query-root" id="the-query-root"><h2>The query root</h2></a>
|
<a class="header" href="#the-query-root" id="the-query-root"><h2>The query root</h2></a>
|
||||||
<p>The query root is just a GraphQL object. You define it like any other GraphQL
|
<p>The query root is just a GraphQL object. You define it like any other GraphQL
|
||||||
object in Juniper, most commonly using the <code>graphql_object!</code> macro:</p>
|
object in Juniper, most commonly using the <code>object</code> proc macro:</p>
|
||||||
<pre><pre class="playpen"><code class="language-rust"># use juniper::FieldResult;
|
<pre><pre class="playpen"><code class="language-rust"># use juniper::FieldResult;
|
||||||
# #[derive(juniper::GraphQLObject)] struct User { name: String }
|
# #[derive(juniper::GraphQLObject)] struct User { name: String }
|
||||||
struct Root;
|
struct Root;
|
||||||
|
|
||||||
juniper::graphql_object!(Root: () |&self| {
|
#[juniper::object]
|
||||||
field userWithUsername(username: String) -> FieldResult<Option<User>> {
|
impl Root {
|
||||||
|
fn userWithUsername(username: String) -> FieldResult<Option<User>> {
|
||||||
// Look up user in database...
|
// Look up user in database...
|
||||||
# unimplemented!()
|
# unimplemented!()
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
# fn main() { }
|
# fn main() { }
|
||||||
</code></pre></pre>
|
</code></pre></pre>
|
||||||
|
@ -173,12 +174,13 @@ usually performs some mutating side-effect, such as updating a database.</p>
|
||||||
# #[derive(juniper::GraphQLObject)] struct User { name: String }
|
# #[derive(juniper::GraphQLObject)] struct User { name: String }
|
||||||
struct Mutations;
|
struct Mutations;
|
||||||
|
|
||||||
juniper::graphql_object!(Mutations: () |&self| {
|
#[juniper::object]
|
||||||
field signUpUser(name: String, email: String) -> FieldResult<User> {
|
impl Mutations {
|
||||||
|
fn signUpUser(name: String, email: String) -> FieldResult<User> {
|
||||||
// Validate inputs and save user in database...
|
// Validate inputs and save user in database...
|
||||||
# unimplemented!()
|
# unimplemented!()
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
# fn main() { }
|
# fn main() { }
|
||||||
</code></pre></pre>
|
</code></pre></pre>
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -173,11 +173,12 @@ fn context_factory(_: &mut Request) -> IronResult<()> {
|
||||||
|
|
||||||
struct Root;
|
struct Root;
|
||||||
|
|
||||||
juniper::graphql_object!(Root: () |&self| {
|
#[juniper::object]
|
||||||
field foo() -> String {
|
impl Root {
|
||||||
|
fn foo() -> String {
|
||||||
"Bar".to_owned()
|
"Bar".to_owned()
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
# #[allow(unreachable_code, unused_variables)]
|
# #[allow(unreachable_code, unused_variables)]
|
||||||
fn main() {
|
fn main() {
|
||||||
|
@ -220,13 +221,14 @@ fn context_factory(req: &mut Request) -> IronResult<Context> {
|
||||||
|
|
||||||
struct Root;
|
struct Root;
|
||||||
|
|
||||||
juniper::graphql_object!(Root: Context |&self| {
|
#[juniper::object(
|
||||||
field my_addr(&executor) -> String {
|
Context = Context,
|
||||||
let context = executor.context();
|
)]
|
||||||
|
impl Root {
|
||||||
|
field my_addr(context: &Context) -> String {
|
||||||
format!("Hello, you're coming from {}", context.remote_addr)
|
format!("Hello, you're coming from {}", context.remote_addr)
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
# fn main() {
|
# fn main() {
|
||||||
# let _graphql_endpoint = juniper_iron::GraphQLHandler::new(
|
# let _graphql_endpoint = juniper_iron::GraphQLHandler::new(
|
||||||
|
@ -236,8 +238,6 @@ juniper::graphql_object!(Root: Context |&self| {
|
||||||
# );
|
# );
|
||||||
# }
|
# }
|
||||||
</code></pre>
|
</code></pre>
|
||||||
<a class="header" href="#accessing-global-data" id="accessing-global-data"><h2>Accessing global data</h2></a>
|
|
||||||
<p>FIXME: Show how the <code>persistent</code> crate works with contexts using e.g. <code>r2d2</code>.</p>
|
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
|
|
@ -149,12 +149,14 @@ struct Coordinate {
|
||||||
struct Root;
|
struct Root;
|
||||||
# #[derive(juniper::GraphQLObject)] struct User { name: String }
|
# #[derive(juniper::GraphQLObject)] struct User { name: String }
|
||||||
|
|
||||||
juniper::graphql_object!(Root: () |&self| {
|
#[juniper::object]
|
||||||
field users_at_location(coordinate: Coordinate, radius: f64) -> Vec<User> {
|
impl Root {
|
||||||
|
fn users_at_location(coordinate: Coordinate, radius: f64) -> Vec<User> {
|
||||||
// Send coordinate to database
|
// Send coordinate to database
|
||||||
|
// ...
|
||||||
# unimplemented!()
|
# unimplemented!()
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
# fn main() {}
|
# fn main() {}
|
||||||
</code></pre></pre>
|
</code></pre></pre>
|
||||||
|
@ -174,12 +176,14 @@ struct WorldCoordinate {
|
||||||
struct Root;
|
struct Root;
|
||||||
# #[derive(juniper::GraphQLObject)] struct User { name: String }
|
# #[derive(juniper::GraphQLObject)] struct User { name: String }
|
||||||
|
|
||||||
juniper::graphql_object!(Root: () |&self| {
|
#[juniper::object]
|
||||||
field users_at_location(coordinate: WorldCoordinate, radius: f64) -> Vec<User> {
|
impl Root {
|
||||||
|
fn users_at_location(coordinate: WorldCoordinate, radius: f64) -> Vec<User> {
|
||||||
// Send coordinate to database
|
// Send coordinate to database
|
||||||
|
// ...
|
||||||
# unimplemented!()
|
# unimplemented!()
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
# fn main() {}
|
# fn main() {}
|
||||||
</code></pre></pre>
|
</code></pre></pre>
|
||||||
|
|
|
@ -204,14 +204,14 @@ you can do away with the downcast methods and use the context instead. Here,
|
||||||
we'll use two hashmaps, but this could be two tables and some SQL calls instead:</p>
|
we'll use two hashmaps, but this could be two tables and some SQL calls instead:</p>
|
||||||
<pre><pre class="playpen"><code class="language-rust"># use std::collections::HashMap;
|
<pre><pre class="playpen"><code class="language-rust"># use std::collections::HashMap;
|
||||||
#[derive(juniper::GraphQLObject)]
|
#[derive(juniper::GraphQLObject)]
|
||||||
#[graphql(Context = "Database")]
|
#[graphql(Context = Database)]
|
||||||
struct Human {
|
struct Human {
|
||||||
id: String,
|
id: String,
|
||||||
home_planet: String,
|
home_planet: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(juniper::GraphQLObject)]
|
#[derive(juniper::GraphQLObject)]
|
||||||
#[graphql(Context = "Database")]
|
#[graphql(Context = Database)]
|
||||||
struct Droid {
|
struct Droid {
|
||||||
id: String,
|
id: String,
|
||||||
primary_function: String,
|
primary_function: String,
|
||||||
|
|
|
@ -139,8 +139,8 @@
|
||||||
<a class="header" href="#complex-fields" id="complex-fields"><h1>Complex fields</h1></a>
|
<a class="header" href="#complex-fields" id="complex-fields"><h1>Complex fields</h1></a>
|
||||||
<p>If you've got a struct that can't be mapped directly to GraphQL, that contains
|
<p>If you've got a struct that can't be mapped directly to GraphQL, that contains
|
||||||
computed fields or circular structures, you have to use a more powerful tool:
|
computed fields or circular structures, you have to use a more powerful tool:
|
||||||
the <code>graphql_object!</code> macro. This macro lets you define GraphQL objects similar
|
the <code>object</code> procedural macro. This macro lets you define GraphQL object
|
||||||
to how you define methods in a Rust <code>impl</code> block for a type. Continuing with the
|
fields in a Rust <code>impl</code> block for a type. Continuing with the
|
||||||
example from the last chapter, this is how you would define <code>Person</code> using the
|
example from the last chapter, this is how you would define <code>Person</code> using the
|
||||||
macro:</p>
|
macro:</p>
|
||||||
<pre><pre class="playpen"><code class="language-rust">
|
<pre><pre class="playpen"><code class="language-rust">
|
||||||
|
@ -149,15 +149,16 @@ struct Person {
|
||||||
age: i32,
|
age: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
juniper::graphql_object!(Person: () |&self| {
|
#[juniper::object]
|
||||||
field name() -> &str {
|
impl Person {
|
||||||
|
fn name(&self) -> &str {
|
||||||
self.name.as_str()
|
self.name.as_str()
|
||||||
}
|
}
|
||||||
|
|
||||||
field age() -> i32 {
|
fn age(&self) -> i32 {
|
||||||
self.age
|
self.age
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
# fn main() { }
|
# fn main() { }
|
||||||
</code></pre></pre>
|
</code></pre></pre>
|
||||||
|
@ -173,12 +174,13 @@ struct House {
|
||||||
inhabitants: Vec<Person>,
|
inhabitants: Vec<Person>,
|
||||||
}
|
}
|
||||||
|
|
||||||
juniper::graphql_object!(House: () |&self| {
|
#[juniper::object]
|
||||||
|
impl House {
|
||||||
// Creates the field inhabitantWithName(name), returning a nullable person
|
// Creates the field inhabitantWithName(name), returning a nullable person
|
||||||
field inhabitant_with_name(name: String) -> Option<&Person> {
|
fn inhabitant_with_name(&self, name: String) -> Option<&Person> {
|
||||||
self.inhabitants.iter().find(|p| p.name == name)
|
self.inhabitants.iter().find(|p| p.name == name)
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
# fn main() {}
|
# fn main() {}
|
||||||
</code></pre></pre>
|
</code></pre></pre>
|
||||||
|
@ -194,15 +196,19 @@ the field. Also, the type name can be changed with an alias:</p>
|
||||||
website_url: String,
|
website_url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
juniper::graphql_object!(Person: () as "PersonObject" |&self| {
|
#[juniper::object(
|
||||||
field name() -> &str {
|
// With this attribtue you can change the public GraphQL name of the type.
|
||||||
|
name = "PersonObject",
|
||||||
|
)]
|
||||||
|
impl Person {
|
||||||
|
fn name(&self) -> &str {
|
||||||
self.name.as_str()
|
self.name.as_str()
|
||||||
}
|
}
|
||||||
|
|
||||||
field websiteURL() -> &str {
|
fn websiteURL(&self) -> &str {
|
||||||
self.website_url.as_str()
|
self.website_url.as_str()
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
# fn main() { }
|
# fn main() { }
|
||||||
</code></pre></pre>
|
</code></pre></pre>
|
||||||
|
@ -213,7 +219,7 @@ juniper::graphql_object!(Person: () as "PersonObject" |&self| {
|
||||||
<li>Per-argument default values</li>
|
<li>Per-argument default values</li>
|
||||||
<li>Per-argument descriptions</li>
|
<li>Per-argument descriptions</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>These, and more features, are described more thorougly in <a href="https://docs.rs/juniper/0.8.1/juniper/macro.graphql_object.html">the reference
|
<p>These, and more features, are described more thorougly in <a href="https://docs.rs/juniper/latest/juniper/macro.object.html">the reference
|
||||||
documentation</a>.</p>
|
documentation</a>.</p>
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
|
@ -263,7 +263,7 @@ attribute:</p>
|
||||||
struct Person {
|
struct Person {
|
||||||
name: String,
|
name: String,
|
||||||
age: i32,
|
age: i32,
|
||||||
#[graphql(deprecation="Please use the name field instead")]
|
#[graphql(deprecated = "Please use the name field instead")]
|
||||||
first_name: String,
|
first_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -159,14 +159,16 @@ struct Example {
|
||||||
filename: PathBuf,
|
filename: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
juniper::graphql_object!(Example: () |&self| {
|
#[juniper::object]
|
||||||
field contents() -> FieldResult<String> {
|
impl Example {
|
||||||
|
fn contents() -> FieldResult<String> {
|
||||||
let mut file = File::open(&self.filename)?;
|
let mut file = File::open(&self.filename)?;
|
||||||
let mut contents = String::new();
|
let mut contents = String::new();
|
||||||
file.read_to_string(&mut contents)?;
|
file.read_to_string(&mut contents)?;
|
||||||
Ok(contents)
|
Ok(contents)
|
||||||
}
|
}
|
||||||
field foo() -> FieldResult<Option<String>> {
|
|
||||||
|
fn foo() -> FieldResult<Option<String>> {
|
||||||
// Some invalid bytes.
|
// Some invalid bytes.
|
||||||
let invalid = vec![128, 223];
|
let invalid = vec![128, 223];
|
||||||
|
|
||||||
|
@ -175,7 +177,7 @@ juniper::graphql_object!(Example: () |&self| {
|
||||||
Err(e) => Err(e)?,
|
Err(e) => Err(e)?,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
# fn main() {}
|
# fn main() {}
|
||||||
</code></pre></pre>
|
</code></pre></pre>
|
||||||
|
@ -255,14 +257,15 @@ struct Example {
|
||||||
whatever: Option<bool>,
|
whatever: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
juniper::graphql_object!(Example: () |&self| {
|
#[juniper::object]
|
||||||
field whatever() -> Result<bool, CustomError> {
|
impl Example {
|
||||||
|
fn whatever() -> Result<bool, CustomError> {
|
||||||
if let Some(value) = self.whatever {
|
if let Some(value) = self.whatever {
|
||||||
return Ok(value);
|
return Ok(value);
|
||||||
}
|
}
|
||||||
Err(CustomError::WhateverNotSet)
|
Err(CustomError::WhateverNotSet)
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
# fn main() {}
|
# fn main() {}
|
||||||
</code></pre></pre>
|
</code></pre></pre>
|
||||||
|
|
|
@ -163,40 +163,53 @@ struct User {
|
||||||
<p>We would like a <code>friends</code> field on <code>User</code> that returns a list of <code>User</code> objects.
|
<p>We would like a <code>friends</code> field on <code>User</code> that returns a list of <code>User</code> objects.
|
||||||
In order to write such a field though, the database must be queried.</p>
|
In order to write such a field though, the database must be queried.</p>
|
||||||
<p>To solve this, we mark the <code>Database</code> as a valid context type and assign it to
|
<p>To solve this, we mark the <code>Database</code> as a valid context type and assign it to
|
||||||
the user object. Then, we use the special <code>&executor</code> argument to access the
|
the user object.</p>
|
||||||
current context object:</p>
|
<p>To gain access to the context, we need to specify an argument with the same
|
||||||
|
type as the specified <code>Context</code> for the type:</p>
|
||||||
<pre><pre class="playpen"><code class="language-rust"># use std::collections::HashMap;
|
<pre><pre class="playpen"><code class="language-rust"># use std::collections::HashMap;
|
||||||
extern crate juniper;
|
extern crate juniper;
|
||||||
|
|
||||||
|
// This struct represents our context.
|
||||||
struct Database {
|
struct Database {
|
||||||
users: HashMap<i32, User>,
|
users: HashMap<i32, User>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mark the Database as a valid context type for Juniper
|
||||||
|
impl juniper::Context for Database {}
|
||||||
|
|
||||||
struct User {
|
struct User {
|
||||||
id: i32,
|
id: i32,
|
||||||
name: String,
|
name: String,
|
||||||
friend_ids: Vec<i32>,
|
friend_ids: Vec<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. Mark the Database as a valid context type for Juniper
|
|
||||||
impl juniper::Context for Database {}
|
|
||||||
|
|
||||||
// 2. Assign Database as the context type for User
|
// Assign Database as the context type for User
|
||||||
juniper::graphql_object!(User: Database |&self| {
|
#[juniper::object(
|
||||||
// 3. Use the special executor argument
|
Context = Database,
|
||||||
field friends(&executor) -> Vec<&User> {
|
)]
|
||||||
// 4. Use the executor to access the context object
|
impl User {
|
||||||
let database = executor.context();
|
// 3. Inject the context by specifying an argument
|
||||||
|
// with the context type.
|
||||||
|
// Note:
|
||||||
|
// - the type must be a reference
|
||||||
|
// - the name of the argument SHOULD be context
|
||||||
|
fn friends(&self, context: &Database) -> Vec<&User> {
|
||||||
|
|
||||||
// 5. Use the database to lookup users
|
// 5. Use the database to lookup users
|
||||||
self.friend_ids.iter()
|
self.friend_ids.iter()
|
||||||
.map(|id| database.users.get(id).expect("Could not find user with ID"))
|
.map(|id| context.users.get(id).expect("Could not find user with ID"))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
field name() -> &str { self.name.as_str() }
|
fn name(&self) -> &str {
|
||||||
field id() -> i32 { self.id }
|
self.name.as_str()
|
||||||
});
|
}
|
||||||
|
|
||||||
|
fn id(&self) -> i32 {
|
||||||
|
self.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# fn main() { }
|
# fn main() { }
|
||||||
</code></pre></pre>
|
</code></pre></pre>
|
||||||
|
|
|
@ -188,14 +188,14 @@ juniper::graphql_union!(<'a> &'a Character: () as "Character"
|
||||||
<p>FIXME: This example does not compile at the moment</p>
|
<p>FIXME: This example does not compile at the moment</p>
|
||||||
<pre><pre class="playpen"><code class="language-rust"># use std::collections::HashMap;
|
<pre><pre class="playpen"><code class="language-rust"># use std::collections::HashMap;
|
||||||
#[derive(juniper::GraphQLObject)]
|
#[derive(juniper::GraphQLObject)]
|
||||||
#[graphql(Context = "Database")]
|
#[graphql(Context = Database)]
|
||||||
struct Human {
|
struct Human {
|
||||||
id: String,
|
id: String,
|
||||||
home_planet: String,
|
home_planet: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(juniper::GraphQLObject)]
|
#[derive(juniper::GraphQLObject)]
|
||||||
#[graphql(Context = "Database")]
|
#[graphql(Context = Database)]
|
||||||
struct Droid {
|
struct Droid {
|
||||||
id: String,
|
id: String,
|
||||||
primary_function: String,
|
primary_function: String,
|
||||||
|
@ -232,14 +232,14 @@ juniper::graphql_union!(<'a> &'a Character: Database as "Characte
|
||||||
<a class="header" href="#placeholder-objects" id="placeholder-objects"><h2>Placeholder objects</h2></a>
|
<a class="header" href="#placeholder-objects" id="placeholder-objects"><h2>Placeholder objects</h2></a>
|
||||||
<pre><pre class="playpen"><code class="language-rust"># use std::collections::HashMap;
|
<pre><pre class="playpen"><code class="language-rust"># use std::collections::HashMap;
|
||||||
#[derive(juniper::GraphQLObject)]
|
#[derive(juniper::GraphQLObject)]
|
||||||
#[graphql(Context = "Database")]
|
#[graphql(Context = Database)]
|
||||||
struct Human {
|
struct Human {
|
||||||
id: String,
|
id: String,
|
||||||
home_planet: String,
|
home_planet: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(juniper::GraphQLObject)]
|
#[derive(juniper::GraphQLObject)]
|
||||||
#[graphql(Context = "Database")]
|
#[graphql(Context = Database)]
|
||||||
struct Droid {
|
struct Droid {
|
||||||
id: String,
|
id: String,
|
||||||
primary_function: String,
|
primary_function: String,
|
||||||
|
|
Loading…
Reference in a new issue