1981 lines
79 KiB
HTML
1981 lines
79 KiB
HTML
<!DOCTYPE HTML>
|
|
<html lang="en" class="sidebar-visible no-js">
|
|
<head>
|
|
<!-- Book generated using mdBook -->
|
|
<meta charset="UTF-8">
|
|
<title>Juniper - GraphQL Server for Rust</title>
|
|
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
|
|
<meta name="description" content="Documentation for juniper, a GraphQL server library for Rust.">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<meta name="theme-color" content="#ffffff" />
|
|
|
|
<link rel="shortcut icon" href="favicon.png">
|
|
<link rel="stylesheet" href="css/variables.css">
|
|
<link rel="stylesheet" href="css/general.css">
|
|
<link rel="stylesheet" href="css/chrome.css">
|
|
<link rel="stylesheet" href="css/print.css" media="print">
|
|
|
|
<!-- Fonts -->
|
|
<link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
|
|
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800" rel="stylesheet" type="text/css">
|
|
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro:500" rel="stylesheet" type="text/css">
|
|
|
|
<!-- Highlight.js Stylesheets -->
|
|
<link rel="stylesheet" href="highlight.css">
|
|
<link rel="stylesheet" href="tomorrow-night.css">
|
|
<link rel="stylesheet" href="ayu-highlight.css">
|
|
|
|
<!-- Custom theme stylesheets -->
|
|
|
|
|
|
|
|
</head>
|
|
<body class="light">
|
|
<!-- Provide site root to javascript -->
|
|
<script type="text/javascript">var path_to_root = "";</script>
|
|
|
|
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
|
<script type="text/javascript">
|
|
try {
|
|
var theme = localStorage.getItem('mdbook-theme');
|
|
var sidebar = localStorage.getItem('mdbook-sidebar');
|
|
|
|
if (theme.startsWith('"') && theme.endsWith('"')) {
|
|
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
|
|
}
|
|
|
|
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
|
|
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
|
|
}
|
|
} catch (e) { }
|
|
</script>
|
|
|
|
<!-- Set the theme before any content is loaded, prevents flash -->
|
|
<script type="text/javascript">
|
|
var theme;
|
|
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
|
if (theme === null || theme === undefined) { theme = 'light'; }
|
|
document.body.className = theme;
|
|
document.querySelector('html').className = theme + ' js';
|
|
</script>
|
|
|
|
<!-- Hide / unhide sidebar before it is displayed -->
|
|
<script type="text/javascript">
|
|
var html = document.querySelector('html');
|
|
var sidebar = 'hidden';
|
|
if (document.body.clientWidth >= 1080) {
|
|
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
|
|
sidebar = sidebar || 'visible';
|
|
}
|
|
html.classList.remove('sidebar-visible');
|
|
html.classList.add("sidebar-" + sidebar);
|
|
</script>
|
|
|
|
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
|
<ol class="chapter"><li class="affix"><a href="index.html">Introduction</a></li><li class="affix"><a href="quickstart.html">Quickstart</a></li><li class="affix"><a href="types/index.html">Type System</a></li><li><a href="types/objects/defining_objects.html"><strong aria-hidden="true">1.</strong> Defining objects</a></li><li><ol class="section"><li><a href="types/objects/complex_fields.html"><strong aria-hidden="true">1.1.</strong> Complex fields</a></li><li><a href="types/objects/using_contexts.html"><strong aria-hidden="true">1.2.</strong> Using contexts</a></li><li><a href="types/objects/error_handling.html"><strong aria-hidden="true">1.3.</strong> Error handling</a></li></ol></li><li><a href="types/other-index.html"><strong aria-hidden="true">2.</strong> Other types</a></li><li><ol class="section"><li><a href="types/enums.html"><strong aria-hidden="true">2.1.</strong> Enums</a></li><li><a href="types/interfaces.html"><strong aria-hidden="true">2.2.</strong> Interfaces</a></li><li><a href="types/input_objects.html"><strong aria-hidden="true">2.3.</strong> Input objects</a></li><li><a href="types/scalars.html"><strong aria-hidden="true">2.4.</strong> Scalars</a></li><li><a href="types/unions.html"><strong aria-hidden="true">2.5.</strong> Unions</a></li></ol></li><li><a href="schema/schemas_and_mutations.html"><strong aria-hidden="true">3.</strong> Schemas and mutations</a></li><li><a href="servers/index.html"><strong aria-hidden="true">4.</strong> Adding A Server</a></li><li><ol class="section"><li><a href="servers/official.html"><strong aria-hidden="true">4.1.</strong> Official Server Integrations</a></li><li><ol class="section"><li><a href="servers/warp.html"><strong aria-hidden="true">4.1.1.</strong> Warp</a></li><li><a href="servers/rocket.html"><strong aria-hidden="true">4.1.2.</strong> Rocket</a></li><li><a href="servers/iron.html"><strong aria-hidden="true">4.1.3.</strong> Iron</a></li><li><a href="servers/hyper.html"><strong aria-hidden="true">4.1.4.</strong> Hyper</a></li></ol></li><li><a href="servers/third-party.html"><strong aria-hidden="true">4.2.</strong> Third Party Integrations</a></li></ol></li><li><a href="advanced/index.html"><strong aria-hidden="true">5.</strong> Advanced Topics</a></li><li><ol class="section"><li><a href="advanced/introspection.html"><strong aria-hidden="true">5.1.</strong> Introspection</a></li><li><a href="advanced/non_struct_objects.html"><strong aria-hidden="true">5.2.</strong> Non-struct objects</a></li><li><a href="advanced/objects_and_generics.html"><strong aria-hidden="true">5.3.</strong> Objects and generics</a></li><li><a href="advanced/multiple_ops_per_request.html"><strong aria-hidden="true">5.4.</strong> Multiple operations per request</a></li></ol></li></ol>
|
|
</nav>
|
|
|
|
<div id="page-wrapper" class="page-wrapper">
|
|
|
|
<div class="page">
|
|
|
|
<div id="menu-bar" class="menu-bar">
|
|
<div id="menu-bar-sticky-container">
|
|
<div class="left-buttons">
|
|
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
|
<i class="fa fa-bars"></i>
|
|
</button>
|
|
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
|
|
<i class="fa fa-paint-brush"></i>
|
|
</button>
|
|
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
|
<li role="none"><button role="menuitem" class="theme" id="light">Light <span class="default">(default)</span></button></li>
|
|
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
|
|
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
|
|
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
|
|
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
|
|
</ul>
|
|
|
|
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
|
|
<i class="fa fa-search"></i>
|
|
</button>
|
|
|
|
</div>
|
|
|
|
<h1 class="menu-title">Juniper - GraphQL Server for Rust</h1>
|
|
|
|
<div class="right-buttons">
|
|
<a href="print.html" title="Print this book" aria-label="Print this book">
|
|
<i id="print-button" class="fa fa-print"></i>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<div id="search-wrapper" class="hidden">
|
|
<form id="searchbar-outer" class="searchbar-outer">
|
|
<input type="search" name="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
|
|
</form>
|
|
<div id="searchresults-outer" class="searchresults-outer hidden">
|
|
<div id="searchresults-header" class="searchresults-header"></div>
|
|
<ul id="searchresults">
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
|
<script type="text/javascript">
|
|
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
|
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
|
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
|
|
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
|
|
});
|
|
</script>
|
|
|
|
<div id="content" class="content">
|
|
<main>
|
|
<a class="header" href="#juniper" id="juniper"><h1>Juniper</h1></a>
|
|
<p>Juniper is a <a href="http://graphql.org">GraphQL</a> server library for Rust. Build type-safe and fast API
|
|
servers with minimal boilerplate and configuration.</p>
|
|
<p><a href="http://graphql.org">GraphQL</a> is a data query language developed by Facebook intended to
|
|
serve mobile and web application frontends.</p>
|
|
<p><em>Juniper</em> makes it possible to write GraphQL servers in Rust that are
|
|
type-safe and blazingly fast. We also try to make declaring and resolving
|
|
GraphQL schemas as convenient as possible as Rust will allow.</p>
|
|
<p>Juniper does not include a web server - instead it provides building blocks to
|
|
make integration with existing servers straightforward. It optionally provides a
|
|
pre-built integration for the <a href="https://hyper.rs">Hyper</a>, <a href="http://ironframework.io">Iron</a>, <a href="https://rocket.rs">Rocket</a>, and <a href="https://github.com/seanmonstar/warp">Warp</a> frameworks, including
|
|
embedded <a href="https://github.com/graphql/graphiql">Graphiql</a> for easy debugging.</p>
|
|
<ul>
|
|
<li><a href="https://crates.io/crates/juniper">Cargo crate</a></li>
|
|
<li><a href="https://docs.rs/juniper">API Reference</a></li>
|
|
</ul>
|
|
<a class="header" href="#features" id="features"><h2>Features</h2></a>
|
|
<p>Juniper supports the full GraphQL query language according to the
|
|
<a href="http://facebook.github.io/graphql">specification</a>, including interfaces, unions, schema
|
|
introspection, and validations.
|
|
It does not, however, support the schema language.</p>
|
|
<p>As an exception to other GraphQL libraries for other languages, Juniper builds
|
|
non-null types by default. A field of type <code>Vec<Episode></code> will be converted into
|
|
<code>[Episode!]!</code>. The corresponding Rust type for e.g. <code>[Episode]</code> would be
|
|
<code>Option<Vec<Option<Episode>>></code>.</p>
|
|
<a class="header" href="#integrations" id="integrations"><h2>Integrations</h2></a>
|
|
<a class="header" href="#data-types" id="data-types"><h3>Data types</h3></a>
|
|
<p>Juniper has automatic integration with some very common Rust crates to make
|
|
building schemas a breeze. The types from these crates will be usable in
|
|
your Schemas automatically.</p>
|
|
<ul>
|
|
<li><a href="https://crates.io/crates/uuid">uuid</a></li>
|
|
<li><a href="https://crates.io/crates/url">url</a></li>
|
|
<li><a href="https://crates.io/crates/chrono">chrono</a></li>
|
|
</ul>
|
|
<a class="header" href="#web-frameworks" id="web-frameworks"><h3>Web Frameworks</h3></a>
|
|
<ul>
|
|
<li><a href="https://hyper.rs">hyper</a></li>
|
|
<li><a href="https://rocket.rs">rocket</a></li>
|
|
<li><a href="http://ironframework.io">iron</a></li>
|
|
<li><a href="https://github.com/seanmonstar/warp">warp</a></li>
|
|
</ul>
|
|
<a class="header" href="#api-stability" id="api-stability"><h2>API Stability</h2></a>
|
|
<p>Juniper has not reached 1.0 yet, thus some API instability should be expected.</p>
|
|
<a class="header" href="#quickstart" id="quickstart"><h1>Quickstart</h1></a>
|
|
<p>This page will give you a short introduction to the concepts in Juniper.</p>
|
|
<a class="header" href="#installation" id="installation"><h2>Installation</h2></a>
|
|
<p>!FILENAME Cargo.toml</p>
|
|
<pre><code class="language-toml">[dependencies]
|
|
juniper = "0.14"
|
|
</code></pre>
|
|
<a class="header" href="#schema-example" id="schema-example"><h2>Schema example</h2></a>
|
|
<p>Exposing simple enums and structs as GraphQL is just a matter of adding a custom
|
|
derive attribute to them. Juniper includes support for basic Rust types that
|
|
naturally map to GraphQL features, such as <code>Option<T></code>, <code>Vec<T></code>, <code>Box<T></code>,
|
|
<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
|
|
types to a GraphQL schema. The most important one is the
|
|
[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>
|
|
<pre><pre class="playpen"><code class="language-rust">use juniper::{FieldResult};
|
|
|
|
# struct DatabasePool;
|
|
# impl DatabasePool {
|
|
# fn get_connection(&self) -> FieldResult<DatabasePool> { Ok(DatabasePool) }
|
|
# fn find_human(&self, _id: &str) -> FieldResult<Human> { Err("")? }
|
|
# fn insert_human(&self, _human: &NewHuman) -> FieldResult<Human> { Err("")? }
|
|
# }
|
|
|
|
#[derive(juniper::GraphQLEnum)]
|
|
enum Episode {
|
|
NewHope,
|
|
Empire,
|
|
Jedi,
|
|
}
|
|
|
|
#[derive(juniper::GraphQLObject)]
|
|
#[graphql(description="A humanoid creature in the Star Wars universe")]
|
|
struct Human {
|
|
id: String,
|
|
name: String,
|
|
appears_in: Vec<Episode>,
|
|
home_planet: String,
|
|
}
|
|
|
|
// There is also a custom derive for mapping GraphQL input objects.
|
|
|
|
#[derive(juniper::GraphQLInputObject)]
|
|
#[graphql(description="A humanoid creature in the Star Wars universe")]
|
|
struct NewHuman {
|
|
name: String,
|
|
appears_in: Vec<Episode>,
|
|
home_planet: String,
|
|
}
|
|
|
|
// Now, we create our root Query and Mutation types with resolvers by using the
|
|
// object macro.
|
|
// Objects can have contexts that allow accessing shared state like a database
|
|
// pool.
|
|
|
|
struct Context {
|
|
// Use your real database pool here.
|
|
pool: DatabasePool,
|
|
}
|
|
|
|
// To make our context usable by Juniper, we have to implement a marker trait.
|
|
impl juniper::Context for Context {}
|
|
|
|
struct Query;
|
|
|
|
#[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 {
|
|
|
|
fn apiVersion() -> &str {
|
|
"1.0"
|
|
}
|
|
|
|
// Arguments to resolvers can either be simple types or input objects.
|
|
// To gain access to the context, we specify a argument
|
|
// that is a reference to the Context type.
|
|
// Juniper automatically injects the correct context here.
|
|
fn human(context: &Context, id: String) -> FieldResult<Human> {
|
|
// Get a db connection.
|
|
let connection = context.pool.get_connection()?;
|
|
// Execute a db query.
|
|
// Note the use of `?` to propagate errors.
|
|
let human = connection.find_human(&id)?;
|
|
// Return the result.
|
|
Ok(human)
|
|
}
|
|
}
|
|
|
|
// Now, we do the same for our Mutation type.
|
|
|
|
struct Mutation;
|
|
|
|
#[juniper::object(
|
|
Context = Context,
|
|
)]
|
|
impl Mutation {
|
|
|
|
fn createHuman(context: &Context, new_human: NewHuman) -> FieldResult<Human> {
|
|
let db = executor.context().pool.get_connection()?;
|
|
let human: Human = db.insert_human(&new_human)?;
|
|
Ok(human)
|
|
}
|
|
}
|
|
|
|
// A root schema consists of a query and a mutation.
|
|
// Request queries can be executed against a RootNode.
|
|
type Schema = juniper::RootNode<'static, Query, Mutation>;
|
|
|
|
# fn main() {
|
|
# let _ = Schema::new(Query, Mutation{});
|
|
# }
|
|
</code></pre></pre>
|
|
<p>We now have a very simple but functional schema for a GraphQL server!</p>
|
|
<p>To actually serve the schema, see the guides for our various <a href="./servers/index.html">server integrations</a>.</p>
|
|
<p>You can also invoke the executor directly to get a result for a query:</p>
|
|
<a class="header" href="#executor" id="executor"><h2>Executor</h2></a>
|
|
<p>You can invoke <code>juniper::execute</code> directly to run a GraphQL query:</p>
|
|
<pre><pre class="playpen"><code class="language-rust"># // Only needed due to 2018 edition because the macro is not accessible.
|
|
# #[macro_use] extern crate juniper;
|
|
use juniper::{FieldResult, Variables, EmptyMutation};
|
|
|
|
|
|
#[derive(juniper::GraphQLEnum, Clone, Copy)]
|
|
enum Episode {
|
|
NewHope,
|
|
Empire,
|
|
Jedi,
|
|
}
|
|
|
|
// Arbitrary context data.
|
|
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.
|
|
// Request queries can be executed against a RootNode.
|
|
type Schema = juniper::RootNode<'static, Query, EmptyMutation<Ctx>>;
|
|
|
|
fn main() {
|
|
// Create a context object.
|
|
let ctx = Ctx(Episode::NewHope);
|
|
|
|
// Run the executor.
|
|
let (res, _errors) = juniper::execute(
|
|
"query { favoriteEpisode }",
|
|
None,
|
|
&Schema::new(Query, EmptyMutation::new()),
|
|
&Variables::new(),
|
|
&ctx,
|
|
).unwrap();
|
|
|
|
// Ensure the value matches.
|
|
assert_eq!(
|
|
res,
|
|
graphql_value!({
|
|
"favoriteEpisode": "NEW_HOPE",
|
|
})
|
|
);
|
|
}
|
|
</code></pre></pre>
|
|
<a class="header" href="#type-system" id="type-system"><h1>Type System</h1></a>
|
|
<p>Most of the work in working with juniper consists of mapping the
|
|
GraphQL type system to the Rust types your application uses.</p>
|
|
<p>Juniper provides some convenient abstractions that try to make this process
|
|
as painless as possible.</p>
|
|
<p>Find out more in the individual chapters below.</p>
|
|
<ul>
|
|
<li><a href="objects/defining_objects.html">Defining objects</a>
|
|
<ul>
|
|
<li><a href="objects/complex_fields.html">Complex fields</a></li>
|
|
<li><a href="objects/using_contexts.html">Using contexts</a></li>
|
|
<li><a href="objects/error_handling.html">Error handling</a></li>
|
|
</ul>
|
|
</li>
|
|
<li><a href="other-index.html">Other types</a>
|
|
<ul>
|
|
<li><a href="enums.html">Enums</a></li>
|
|
<li><a href="interfaces.html">Interfaces</a></li>
|
|
<li><a href="input_objects.html">Input objects</a></li>
|
|
<li><a href="scalars.html">Scalars</a></li>
|
|
<li><a href="unions.html">Unions</a></li>
|
|
</ul>
|
|
</li>
|
|
</ul>
|
|
<a class="header" href="#defining-objects" id="defining-objects"><h1>Defining objects</h1></a>
|
|
<p>While any type in Rust can be exposed as a GraphQL object, the most common one
|
|
is a struct.</p>
|
|
<p>There are two ways to create a GraphQL object in Juniper. If you've got a simple
|
|
struct you want to expose, the easiest way is to use the custom derive
|
|
attribute. The other way is described in the <a href="complex_fields.html">Complex fields</a>
|
|
chapter.</p>
|
|
<pre><pre class="playpen"><code class="language-rust">#[derive(juniper::GraphQLObject)]
|
|
struct Person {
|
|
name: String,
|
|
age: i32,
|
|
}
|
|
|
|
# fn main() {}
|
|
</code></pre></pre>
|
|
<p>This will create a GraphQL object type called <code>Person</code>, with two fields: <code>name</code>
|
|
of type <code>String!</code>, and <code>age</code> of type <code>Int!</code>. Because of Rust's type system,
|
|
everything is exported as non-null by default. If you need a nullable field, you
|
|
can use <code>Option<T></code>.</p>
|
|
<p>We should take advantage of the
|
|
fact that GraphQL is self-documenting and add descriptions to the type and
|
|
fields. Juniper will automatically use associated doc comments as GraphQL
|
|
descriptions:</p>
|
|
<p>!FILENAME GraphQL descriptions via Rust doc comments</p>
|
|
<pre><pre class="playpen"><code class="language-rust">#[derive(juniper::GraphQLObject)]
|
|
/// Information about a person
|
|
struct Person {
|
|
/// The person's full name, including both first and last names
|
|
name: String,
|
|
/// The person's age in years, rounded down
|
|
age: i32,
|
|
}
|
|
|
|
# fn main() {}
|
|
</code></pre></pre>
|
|
<p>Objects and fields without doc comments can instead set a <code>description</code>
|
|
via the <code>graphql</code> attribute. The following example is equivalent to the above:</p>
|
|
<p>!FILENAME GraphQL descriptions via attribute</p>
|
|
<pre><pre class="playpen"><code class="language-rust">#[derive(juniper::GraphQLObject)]
|
|
#[graphql(description="Information about a person")]
|
|
struct Person {
|
|
#[graphql(description="The person's full name, including both first and last names")]
|
|
name: String,
|
|
#[graphql(description="The person's age in years, rounded down")]
|
|
age: i32,
|
|
}
|
|
|
|
# fn main() {}
|
|
</code></pre></pre>
|
|
<p>Descriptions set via the <code>graphql</code> attribute take precedence over Rust
|
|
doc comments. This enables internal Rust documentation and external GraphQL
|
|
documentation to differ:</p>
|
|
<pre><pre class="playpen"><code class="language-rust">#[derive(juniper::GraphQLObject)]
|
|
#[graphql(description="This description shows up in GraphQL")]
|
|
/// This description shows up in RustDoc
|
|
struct Person {
|
|
#[graphql(description="This description shows up in GraphQL")]
|
|
/// This description shows up in RustDoc
|
|
name: String,
|
|
/// This description shows up in both RustDoc and GraphQL
|
|
age: i32,
|
|
}
|
|
|
|
# fn main() {}
|
|
</code></pre></pre>
|
|
<a class="header" href="#relationships" id="relationships"><h2>Relationships</h2></a>
|
|
<p>You can only use the custom derive attribute under these circumstances:</p>
|
|
<ul>
|
|
<li>The annotated type is a <code>struct</code>,</li>
|
|
<li>Every struct field is either
|
|
<ul>
|
|
<li>A primitive type (<code>i32</code>, <code>f64</code>, <code>bool</code>, <code>String</code>, <code>juniper::ID</code>), or</li>
|
|
<li>A valid custom GraphQL type, e.g. another struct marked with this attribute,
|
|
or</li>
|
|
<li>A container/reference containing any of the above, e.g. <code>Vec<T></code>, <code>Box<T></code>,
|
|
<code>Option<T></code></li>
|
|
</ul>
|
|
</li>
|
|
</ul>
|
|
<p>Let's see what that means for building relationships between objects:</p>
|
|
<pre><pre class="playpen"><code class="language-rust">#[derive(juniper::GraphQLObject)]
|
|
struct Person {
|
|
name: String,
|
|
age: i32,
|
|
}
|
|
|
|
#[derive(juniper::GraphQLObject)]
|
|
struct House {
|
|
address: Option<String>, // Converted into String (nullable)
|
|
inhabitants: Vec<Person>, // Converted into [Person!]!
|
|
}
|
|
|
|
# fn main() {}
|
|
</code></pre></pre>
|
|
<p>Because <code>Person</code> is a valid GraphQL type, you can have a <code>Vec<Person></code> in a
|
|
struct and it'll be automatically converted into a list of non-nullable <code>Person</code>
|
|
objects.</p>
|
|
<a class="header" href="#renaming-fields" id="renaming-fields"><h2>Renaming fields</h2></a>
|
|
<p>By default, struct fields are converted from Rust's standard <code>snake_case</code> naming
|
|
convention into GraphQL's <code>camelCase</code> convention:</p>
|
|
<pre><pre class="playpen"><code class="language-rust">#[derive(juniper::GraphQLObject)]
|
|
struct Person {
|
|
first_name: String, // Would be exposed as firstName in the GraphQL schema
|
|
last_name: String, // Exposed as lastName
|
|
}
|
|
|
|
# fn main() {}
|
|
</code></pre></pre>
|
|
<p>You can override the name by using the <code>graphql</code> attribute on individual struct
|
|
fields:</p>
|
|
<pre><pre class="playpen"><code class="language-rust">#[derive(juniper::GraphQLObject)]
|
|
struct Person {
|
|
name: String,
|
|
age: i32,
|
|
#[graphql(name="websiteURL")]
|
|
website_url: Option<String>, // Now exposed as websiteURL in the schema
|
|
}
|
|
|
|
# fn main() {}
|
|
</code></pre></pre>
|
|
<a class="header" href="#deprecating-fields" id="deprecating-fields"><h2>Deprecating fields</h2></a>
|
|
<p>To deprecate a field, you specify a deprecation reason using the <code>graphql</code>
|
|
attribute:</p>
|
|
<pre><pre class="playpen"><code class="language-rust">#[derive(juniper::GraphQLObject)]
|
|
struct Person {
|
|
name: String,
|
|
age: i32,
|
|
#[graphql(deprecated = "Please use the name field instead")]
|
|
first_name: String,
|
|
}
|
|
|
|
# fn main() {}
|
|
</code></pre></pre>
|
|
<p>The <code>name</code>, <code>description</code>, and <code>deprecation</code> arguments can of course be
|
|
combined. Some restrictions from the GraphQL spec still applies though; you can
|
|
only deprecate object fields and enum values.</p>
|
|
<a class="header" href="#skipping-fields" id="skipping-fields"><h2>Skipping fields</h2></a>
|
|
<p>By default all fields in a <code>GraphQLObject</code> are included in the generated GraphQL type. To prevent including a specific field, annotate the field with <code>#[graphql(skip)]</code>:</p>
|
|
<pre><pre class="playpen"><code class="language-rust">#[derive(juniper::GraphQLObject)]
|
|
struct Person {
|
|
name: String,
|
|
age: i32,
|
|
#[graphql(skip)]
|
|
# #[allow(dead_code)]
|
|
password_hash: String, // This cannot be queried or modified from GraphQL
|
|
}
|
|
|
|
# fn main() {}
|
|
</code></pre></pre>
|
|
<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
|
|
computed fields or circular structures, you have to use a more powerful tool:
|
|
the <code>object</code> procedural macro. This macro lets you define GraphQL object
|
|
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
|
|
macro:</p>
|
|
<pre><pre class="playpen"><code class="language-rust">
|
|
struct Person {
|
|
name: String,
|
|
age: i32,
|
|
}
|
|
|
|
#[juniper::object]
|
|
impl Person {
|
|
fn name(&self) -> &str {
|
|
self.name.as_str()
|
|
}
|
|
|
|
fn age(&self) -> i32 {
|
|
self.age
|
|
}
|
|
}
|
|
|
|
# fn main() { }
|
|
</code></pre></pre>
|
|
<p>While this is a bit more verbose, it lets you write any kind of function in the
|
|
field resolver. With this syntax, fields can also take arguments:</p>
|
|
<pre><pre class="playpen"><code class="language-rust">#[derive(juniper::GraphQLObject)]
|
|
struct Person {
|
|
name: String,
|
|
age: i32,
|
|
}
|
|
|
|
struct House {
|
|
inhabitants: Vec<Person>,
|
|
}
|
|
|
|
#[juniper::object]
|
|
impl House {
|
|
// Creates the field inhabitantWithName(name), returning a nullable person
|
|
fn inhabitant_with_name(&self, name: String) -> Option<&Person> {
|
|
self.inhabitants.iter().find(|p| p.name == name)
|
|
}
|
|
}
|
|
|
|
# fn main() {}
|
|
</code></pre></pre>
|
|
<p>To access global data such as database connections or authentication
|
|
information, a <em>context</em> is used. To learn more about this, see the next
|
|
chapter: <a href="using_contexts.html">Using contexts</a>.</p>
|
|
<a class="header" href="#description-renaming-and-deprecation" id="description-renaming-and-deprecation"><h2>Description, renaming, and deprecation</h2></a>
|
|
<p>Like with the derive attribute, field names will be converted from <code>snake_case</code>
|
|
to <code>camelCase</code>. If you need to override the conversion, you can simply rename
|
|
the field. Also, the type name can be changed with an alias:</p>
|
|
<pre><pre class="playpen"><code class="language-rust">
|
|
struct Person {
|
|
}
|
|
|
|
/// Doc comments are used as descriptions for GraphQL.
|
|
#[juniper::object(
|
|
// With this attribtue you can change the public GraphQL name of the type.
|
|
name = "PersonObject",
|
|
// You can also specify a description here, which will overwrite
|
|
// a doc comment description.
|
|
description = "...",
|
|
)]
|
|
impl Person {
|
|
|
|
/// A doc comment on the field will also be used for GraphQL.
|
|
#[graphql(
|
|
// Or provide a description here.
|
|
description = "...",
|
|
)]
|
|
fn doc_comment(&self) -> &str {
|
|
""
|
|
}
|
|
|
|
// Fields can also be renamed if required.
|
|
#[graphql(
|
|
name = "myCustomFieldName",
|
|
)]
|
|
fn renamed_field() -> bool {
|
|
true
|
|
}
|
|
|
|
// Deprecations also work as you'd expect.
|
|
// Both the standard Rust syntax and a custom attribute is accepted.
|
|
#[deprecated(note = "...")]
|
|
fn deprecated_standard() -> bool {
|
|
false
|
|
}
|
|
|
|
#[graphql(deprecated = "...")]
|
|
fn deprecated_graphql() -> bool {
|
|
true
|
|
}
|
|
}
|
|
|
|
# fn main() { }
|
|
</code></pre></pre>
|
|
<a class="header" href="#customizing-arguments" id="customizing-arguments"><h2>Customizing arguments</h2></a>
|
|
<p>Method field arguments can also be customized.</p>
|
|
<p>They can have custom descriptions and default values.</p>
|
|
<p><strong>Note</strong>: The syntax for this is currently a little awkward.
|
|
This will become better once the <a href="https://github.com/rust-lang/rust/issues/60406">Rust RFC 2565</a> is implemented.</p>
|
|
<pre><pre class="playpen"><code class="language-rust">
|
|
struct Person {}
|
|
|
|
#[juniper::object]
|
|
impl Person {
|
|
#[graphql(
|
|
arguments(
|
|
arg1(
|
|
// Set a default value which will be injected if not present.
|
|
// The default can be any valid Rust expression, including a function call, etc.
|
|
default = true,
|
|
// Set a description.
|
|
description = "The first argument..."
|
|
),
|
|
arg2(
|
|
default = 0,
|
|
)
|
|
)
|
|
)]
|
|
fn field1(&self, arg1: bool, arg2: i32) -> String {
|
|
format!("{} {}", arg1, arg2)
|
|
}
|
|
}
|
|
|
|
# fn main() { }
|
|
</code></pre></pre>
|
|
<a class="header" href="#more-features" id="more-features"><h2>More features</h2></a>
|
|
<p>GraphQL fields expose more features than Rust's standard method syntax gives us:</p>
|
|
<ul>
|
|
<li>Per-field description and deprecation messages</li>
|
|
<li>Per-argument default values</li>
|
|
<li>Per-argument descriptions</li>
|
|
</ul>
|
|
<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>
|
|
<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
|
|
data, most commonly database connections or authentication information. The
|
|
context is usually created from a <em>context factory</em>. How this is defined is
|
|
specific to the framework integration you're using, so check out the
|
|
documentation for either the <a href="../../servers/iron.html">Iron</a> or <a href="../../servers/rocket.html">Rocket</a>
|
|
integration.</p>
|
|
<p>In this chapter, we'll show you how to define a context type and use it in field
|
|
resolvers. Let's say that we have a simple user database in a <code>HashMap</code>:</p>
|
|
<pre><pre class="playpen"><code class="language-rust"># #![allow(dead_code)]
|
|
# use std::collections::HashMap;
|
|
|
|
struct Database {
|
|
users: HashMap<i32, User>,
|
|
}
|
|
|
|
struct User {
|
|
id: i32,
|
|
name: String,
|
|
friend_ids: Vec<i32>,
|
|
}
|
|
|
|
# fn main() { }
|
|
</code></pre></pre>
|
|
<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>
|
|
<p>To solve this, we mark the <code>Database</code> as a valid context type and assign it to
|
|
the user 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;
|
|
extern crate juniper;
|
|
|
|
// This struct represents our context.
|
|
struct Database {
|
|
users: HashMap<i32, User>,
|
|
}
|
|
|
|
// Mark the Database as a valid context type for Juniper
|
|
impl juniper::Context for Database {}
|
|
|
|
struct User {
|
|
id: i32,
|
|
name: String,
|
|
friend_ids: Vec<i32>,
|
|
}
|
|
|
|
|
|
// Assign Database as the context type for User
|
|
#[juniper::object(
|
|
Context = Database,
|
|
)]
|
|
impl User {
|
|
// 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
|
|
self.friend_ids.iter()
|
|
.map(|id| context.users.get(id).expect("Could not find user with ID"))
|
|
.collect()
|
|
}
|
|
|
|
fn name(&self) -> &str {
|
|
self.name.as_str()
|
|
}
|
|
|
|
fn id(&self) -> i32 {
|
|
self.id
|
|
}
|
|
}
|
|
|
|
# fn main() { }
|
|
</code></pre></pre>
|
|
<p>You only get an immutable reference to the context, so if you want to affect
|
|
change to the execution, you'll need to use <a href="https://doc.rust-lang.org/book/first-edition/mutability.html#interior-vs-exterior-mutability">interior
|
|
mutability</a>
|
|
using e.g. <code>RwLock</code> or <code>RefCell</code>.</p>
|
|
<a class="header" href="#error-handling" id="error-handling"><h1>Error handling</h1></a>
|
|
<p>Rust
|
|
<a href="https://doc.rust-lang.org/book/second-edition/ch09-00-error-handling.html">provides</a>
|
|
two ways of dealing with errors: <code>Result<T, E></code> for recoverable errors and
|
|
<code>panic!</code> for unrecoverable errors. Juniper does not do anything about panicking;
|
|
it will bubble up to the surrounding framework and hopefully be dealt with
|
|
there.</p>
|
|
<p>For recoverable errors, Juniper works well with the built-in <code>Result</code> type, you
|
|
can use the <code>?</code> operator or the <code>try!</code> macro and things will generally just work
|
|
as you expect them to:</p>
|
|
<pre><pre class="playpen"><code class="language-rust"># extern crate juniper;
|
|
use std::{
|
|
str,
|
|
path::PathBuf,
|
|
fs::{File},
|
|
io::{Read},
|
|
};
|
|
use juniper::FieldResult;
|
|
|
|
struct Example {
|
|
filename: PathBuf,
|
|
}
|
|
|
|
#[juniper::object]
|
|
impl Example {
|
|
fn contents() -> FieldResult<String> {
|
|
let mut file = File::open(&self.filename)?;
|
|
let mut contents = String::new();
|
|
file.read_to_string(&mut contents)?;
|
|
Ok(contents)
|
|
}
|
|
|
|
fn foo() -> FieldResult<Option<String>> {
|
|
// Some invalid bytes.
|
|
let invalid = vec![128, 223];
|
|
|
|
match str::from_utf8(&invalid) {
|
|
Ok(s) => Ok(Some(s.to_string())),
|
|
Err(e) => Err(e)?,
|
|
}
|
|
}
|
|
}
|
|
|
|
# fn main() {}
|
|
</code></pre></pre>
|
|
<p><code>FieldResult<T></code> is an alias for <code>Result<T, FieldError></code>, which is the error
|
|
type all fields must return. By using the <code>?</code> operator or <code>try!</code> macro, any type
|
|
that implements the <code>Display</code> trait - which are most of the error types out
|
|
there - those errors are automatically converted into <code>FieldError</code>.</p>
|
|
<p>When a field returns an error, the field's result is replaced by <code>null</code>, an
|
|
additional <code>errors</code> object is created at the top level of the response, and the
|
|
execution is resumed. For example, with the previous example and the following
|
|
query:</p>
|
|
<pre><code class="language-graphql">{
|
|
example {
|
|
contents
|
|
foo
|
|
}
|
|
}
|
|
</code></pre>
|
|
<p>If <code>str::from_utf8</code> resulted in a <code>std::str::Utf8Error</code>, the following would be
|
|
returned:</p>
|
|
<p>!FILENAME Response for nullable field with error</p>
|
|
<pre><code class="language-js">{
|
|
"data": {
|
|
"example": {
|
|
contents: "<Contents of the file>",
|
|
foo: null,
|
|
}
|
|
},
|
|
"errors": [
|
|
"message": "invalid utf-8 sequence of 2 bytes from index 0",
|
|
"locations": [{ "line": 2, "column": 4 }])
|
|
]
|
|
}
|
|
</code></pre>
|
|
<p>If an error is returned from a non-null field, such as the
|
|
example above, the <code>null</code> value is propagated up to the first nullable parent
|
|
field, or the root <code>data</code> object if there are no nullable fields.</p>
|
|
<p>For example, with the following query:</p>
|
|
<pre><code class="language-graphql">{
|
|
example {
|
|
contents
|
|
}
|
|
}
|
|
</code></pre>
|
|
<p>If <code>File::open()</code> above resulted in <code>std::io::ErrorKind::PermissionDenied</code>, the
|
|
following would be returned:</p>
|
|
<p>!FILENAME Response for non-null field with error and no nullable parent</p>
|
|
<pre><code class="language-js">{
|
|
"errors": [
|
|
"message": "Permission denied (os error 13)",
|
|
"locations": [{ "line": 2, "column": 4 }])
|
|
]
|
|
}
|
|
</code></pre>
|
|
<a class="header" href="#structured-errors" id="structured-errors"><h2>Structured errors</h2></a>
|
|
<p>Sometimes it is desirable to return additional structured error information
|
|
to clients. This can be accomplished by implementing <a href="https://docs.rs/juniper/latest/juniper/trait.IntoFieldError.html"><code>IntoFieldError</code></a>:</p>
|
|
<pre><pre class="playpen"><code class="language-rust"># #[macro_use] extern crate juniper;
|
|
enum CustomError {
|
|
WhateverNotSet,
|
|
}
|
|
|
|
impl juniper::IntoFieldError for CustomError {
|
|
fn into_field_error(self) -> juniper::FieldError {
|
|
match self {
|
|
CustomError::WhateverNotSet => juniper::FieldError::new(
|
|
"Whatever does not exist",
|
|
graphql_value!({
|
|
"type": "NO_WHATEVER"
|
|
}),
|
|
),
|
|
}
|
|
}
|
|
}
|
|
|
|
struct Example {
|
|
whatever: Option<bool>,
|
|
}
|
|
|
|
#[juniper::object]
|
|
impl Example {
|
|
fn whatever() -> Result<bool, CustomError> {
|
|
if let Some(value) = self.whatever {
|
|
return Ok(value);
|
|
}
|
|
Err(CustomError::WhateverNotSet)
|
|
}
|
|
}
|
|
|
|
# fn main() {}
|
|
</code></pre></pre>
|
|
<p>The specified structured error information is included in the <a href="https://facebook.github.io/graphql/June2018/#sec-Errors"><code>extensions</code></a> key:</p>
|
|
<pre><code class="language-js">{
|
|
"errors": [
|
|
"message": "Whatever does not exist",
|
|
"locations": [{ "line": 2, "column": 4 }]),
|
|
"extensions": {
|
|
"type": "NO_WHATEVER"
|
|
}
|
|
]
|
|
}
|
|
</code></pre>
|
|
<a class="header" href="#other-types" id="other-types"><h1>Other Types</h1></a>
|
|
<p>The GraphQL type system provides several types in additon to objects.</p>
|
|
<p>Find out more about each type below:</p>
|
|
<ul>
|
|
<li><a href="enums.html">Enums</a></li>
|
|
<li><a href="interfaces.html">Interfaces</a></li>
|
|
<li><a href="input_objects.html">Input objects</a></li>
|
|
<li><a href="scalars.html">Scalars</a></li>
|
|
<li><a href="unions.html">Unions</a></li>
|
|
</ul>
|
|
<a class="header" href="#enums" id="enums"><h1>Enums</h1></a>
|
|
<p>Enums in GraphQL are string constants grouped together to represent a set of
|
|
possible values. Simple Rust enums can be converted to GraphQL enums by using a
|
|
custom derive attribute:</p>
|
|
<pre><pre class="playpen"><code class="language-rust">#[derive(juniper::GraphQLEnum)]
|
|
enum Episode {
|
|
NewHope,
|
|
Empire,
|
|
Jedi,
|
|
}
|
|
|
|
# fn main() {}
|
|
</code></pre></pre>
|
|
<p>Juniper converts all enum variants to uppercase, so the corresponding string
|
|
values for these variants are <code>NEWHOPE</code>, <code>EMPIRE</code>, and <code>JEDI</code>, respectively. If
|
|
you want to override this, you can use the <code>graphql</code> attribute, similar to how
|
|
it works when <a href="objects/defining_objects.html">defining objects</a>:</p>
|
|
<pre><pre class="playpen"><code class="language-rust">#[derive(juniper::GraphQLEnum)]
|
|
enum Episode {
|
|
#[graphql(name="NEW_HOPE")]
|
|
NewHope,
|
|
Empire,
|
|
Jedi,
|
|
}
|
|
|
|
# fn main() {}
|
|
</code></pre></pre>
|
|
<a class="header" href="#documentation-and-deprecation" id="documentation-and-deprecation"><h2>Documentation and deprecation</h2></a>
|
|
<p>Just like when defining objects, the type itself can be renamed and documented,
|
|
while individual enum variants can be renamed, documented, and deprecated:</p>
|
|
<pre><pre class="playpen"><code class="language-rust">#[derive(juniper::GraphQLEnum)]
|
|
#[graphql(name="Episode", description="An episode of Star Wars")]
|
|
enum StarWarsEpisode {
|
|
#[graphql(deprecated="We don't really talk about this one")]
|
|
ThePhantomMenace,
|
|
|
|
#[graphql(name="NEW_HOPE")]
|
|
NewHope,
|
|
|
|
#[graphql(description="Arguably the best one in the trilogy")]
|
|
Empire,
|
|
Jedi,
|
|
}
|
|
|
|
# fn main() {}
|
|
</code></pre></pre>
|
|
<a class="header" href="#interfaces" id="interfaces"><h1>Interfaces</h1></a>
|
|
<p>GraphQL interfaces map well to interfaces known from common object-oriented
|
|
languages such as Java or C#, but Rust has unfortunately not a concept that maps
|
|
perfectly to them. Because of this, defining interfaces in Juniper can require a
|
|
little bit of boilerplate code, but on the other hand gives you full control
|
|
over which type is backing your interface.</p>
|
|
<p>To highlight a couple of different ways you can implement interfaces in Rust,
|
|
let's have a look at the same end-result from a few different implementations:</p>
|
|
<a class="header" href="#traits" id="traits"><h2>Traits</h2></a>
|
|
<p>Traits are maybe the most obvious concept you want to use when building
|
|
interfaces. But because GraphQL supports downcasting while Rust doesn't, you'll
|
|
have to manually specify how to convert a trait into a concrete type. This can
|
|
be done in a couple of different ways:</p>
|
|
<a class="header" href="#downcasting-via-accessor-methods" id="downcasting-via-accessor-methods"><h3>Downcasting via accessor methods</h3></a>
|
|
<pre><pre class="playpen"><code class="language-rust">#[derive(juniper::GraphQLObject)]
|
|
struct Human {
|
|
id: String,
|
|
home_planet: String,
|
|
}
|
|
|
|
#[derive(juniper::GraphQLObject)]
|
|
struct Droid {
|
|
id: String,
|
|
primary_function: String,
|
|
}
|
|
|
|
trait Character {
|
|
fn id(&self) -> &str;
|
|
|
|
// Downcast methods, each concrete class will need to implement one of these
|
|
fn as_human(&self) -> Option<&Human> { None }
|
|
fn as_droid(&self) -> Option<&Droid> { None }
|
|
}
|
|
|
|
impl Character for Human {
|
|
fn id(&self) -> &str { self.id.as_str() }
|
|
fn as_human(&self) -> Option<&Human> { Some(&self) }
|
|
}
|
|
|
|
impl Character for Droid {
|
|
fn id(&self) -> &str { self.id.as_str() }
|
|
fn as_droid(&self) -> Option<&Droid> { Some(&self) }
|
|
}
|
|
|
|
juniper::graphql_interface!(<'a> &'a Character: () as "Character" where Scalar = <S> |&self| {
|
|
field id() -> &str { self.id() }
|
|
|
|
instance_resolvers: |_| {
|
|
// The left hand side indicates the concrete type T, the right hand
|
|
// side should be an expression returning Option<T>
|
|
&Human => self.as_human(),
|
|
&Droid => self.as_droid(),
|
|
}
|
|
});
|
|
|
|
# fn main() {}
|
|
</code></pre></pre>
|
|
<p>The <code>instance_resolvers</code> declaration lists all the implementors of the given
|
|
interface and how to resolve them.</p>
|
|
<p>As you can see, you lose a bit of the point with using traits: you need to list
|
|
all the concrete types in the trait itself, and there's a bit of repetition
|
|
going on.</p>
|
|
<a class="header" href="#using-an-extra-database-lookup" id="using-an-extra-database-lookup"><h3>Using an extra database lookup</h3></a>
|
|
<p>If you can afford an extra database lookup when the concrete class is requested,
|
|
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>
|
|
<pre><pre class="playpen"><code class="language-rust"># use std::collections::HashMap;
|
|
#[derive(juniper::GraphQLObject)]
|
|
#[graphql(Context = Database)]
|
|
struct Human {
|
|
id: String,
|
|
home_planet: String,
|
|
}
|
|
|
|
#[derive(juniper::GraphQLObject)]
|
|
#[graphql(Context = Database)]
|
|
struct Droid {
|
|
id: String,
|
|
primary_function: String,
|
|
}
|
|
|
|
struct Database {
|
|
humans: HashMap<String, Human>,
|
|
droids: HashMap<String, Droid>,
|
|
}
|
|
|
|
impl juniper::Context for Database {}
|
|
|
|
trait Character {
|
|
fn id(&self) -> &str;
|
|
}
|
|
|
|
impl Character for Human {
|
|
fn id(&self) -> &str { self.id.as_str() }
|
|
}
|
|
|
|
impl Character for Droid {
|
|
fn id(&self) -> &str { self.id.as_str() }
|
|
}
|
|
|
|
juniper::graphql_interface!(<'a> &'a Character: Database as "Character" where Scalar = <S> |&self| {
|
|
field id() -> &str { self.id() }
|
|
|
|
instance_resolvers: |&context| {
|
|
&Human => context.humans.get(self.id()),
|
|
&Droid => context.droids.get(self.id()),
|
|
}
|
|
});
|
|
|
|
# fn main() {}
|
|
</code></pre></pre>
|
|
<p>This removes the need of downcast methods, but still requires some repetition.</p>
|
|
<a class="header" href="#placeholder-objects" id="placeholder-objects"><h2>Placeholder objects</h2></a>
|
|
<p>Continuing on from the last example, the trait itself seems a bit unneccesary.
|
|
Maybe it can just be a struct containing the ID?</p>
|
|
<pre><pre class="playpen"><code class="language-rust"># use std::collections::HashMap;
|
|
#[derive(juniper::GraphQLObject)]
|
|
#[graphql(Context = "Database")]
|
|
struct Human {
|
|
id: String,
|
|
home_planet: String,
|
|
}
|
|
|
|
#[derive(juniper::GraphQLObject)]
|
|
#[graphql(Context = "Database")]
|
|
struct Droid {
|
|
id: String,
|
|
primary_function: String,
|
|
}
|
|
|
|
struct Database {
|
|
humans: HashMap<String, Human>,
|
|
droids: HashMap<String, Droid>,
|
|
}
|
|
|
|
impl juniper::Context for Database {}
|
|
|
|
struct Character {
|
|
id: String,
|
|
}
|
|
|
|
juniper::graphql_interface!(Character: Database where Scalar = <S> |&self| {
|
|
field id() -> &str { self.id.as_str() }
|
|
|
|
instance_resolvers: |&context| {
|
|
&Human => context.humans.get(&self.id),
|
|
&Droid => context.droids.get(&self.id),
|
|
}
|
|
});
|
|
|
|
# fn main() {}
|
|
</code></pre></pre>
|
|
<p>This reduces repetition some more, but might be impractical if the interface's
|
|
surface area is large.</p>
|
|
<a class="header" href="#enums-1" id="enums-1"><h2>Enums</h2></a>
|
|
<p>Using enums and pattern matching lies half-way between using traits and using
|
|
placeholder objects. We don't need the extra database call in this case, so
|
|
we'll remove it.</p>
|
|
<pre><pre class="playpen"><code class="language-rust">#[derive(juniper::GraphQLObject)]
|
|
struct Human {
|
|
id: String,
|
|
home_planet: String,
|
|
}
|
|
|
|
#[derive(juniper::GraphQLObject)]
|
|
struct Droid {
|
|
id: String,
|
|
primary_function: String,
|
|
}
|
|
|
|
# #[allow(dead_code)]
|
|
enum Character {
|
|
Human(Human),
|
|
Droid(Droid),
|
|
}
|
|
|
|
juniper::graphql_interface!(Character: () where Scalar = <S> |&self| {
|
|
field id() -> &str {
|
|
match *self {
|
|
Character::Human(Human { ref id, .. }) |
|
|
Character::Droid(Droid { ref id, .. }) => id,
|
|
}
|
|
}
|
|
|
|
instance_resolvers: |_| {
|
|
&Human => match *self { Character::Human(ref h) => Some(h), _ => None },
|
|
&Droid => match *self { Character::Droid(ref d) => Some(d), _ => None },
|
|
}
|
|
});
|
|
|
|
# fn main() {}
|
|
</code></pre></pre>
|
|
<a class="header" href="#input-objects" id="input-objects"><h1>Input objects</h1></a>
|
|
<p>Input objects are complex data structures that can be used as arguments to
|
|
GraphQL fields. In Juniper, you can define input objects using a custom derive
|
|
attribute, similar to simple objects and enums:</p>
|
|
<pre><pre class="playpen"><code class="language-rust">#[derive(juniper::GraphQLInputObject)]
|
|
struct Coordinate {
|
|
latitude: f64,
|
|
longitude: f64
|
|
}
|
|
|
|
struct Root;
|
|
# #[derive(juniper::GraphQLObject)] struct User { name: String }
|
|
|
|
#[juniper::object]
|
|
impl Root {
|
|
fn users_at_location(coordinate: Coordinate, radius: f64) -> Vec<User> {
|
|
// Send coordinate to database
|
|
// ...
|
|
# unimplemented!()
|
|
}
|
|
}
|
|
|
|
# fn main() {}
|
|
</code></pre></pre>
|
|
<a class="header" href="#documentation-and-renaming" id="documentation-and-renaming"><h2>Documentation and renaming</h2></a>
|
|
<p>Just like the <a href="objects/defining_objects.html">other</a> <a href="enums.html">derives</a>, you can rename
|
|
and add documentation to both the type and the fields:</p>
|
|
<pre><pre class="playpen"><code class="language-rust">#[derive(juniper::GraphQLInputObject)]
|
|
#[graphql(name="Coordinate", description="A position on the globe")]
|
|
struct WorldCoordinate {
|
|
#[graphql(name="lat", description="The latitude")]
|
|
latitude: f64,
|
|
|
|
#[graphql(name="long", description="The longitude")]
|
|
longitude: f64
|
|
}
|
|
|
|
struct Root;
|
|
# #[derive(juniper::GraphQLObject)] struct User { name: String }
|
|
|
|
#[juniper::object]
|
|
impl Root {
|
|
fn users_at_location(coordinate: WorldCoordinate, radius: f64) -> Vec<User> {
|
|
// Send coordinate to database
|
|
// ...
|
|
# unimplemented!()
|
|
}
|
|
}
|
|
|
|
# fn main() {}
|
|
</code></pre></pre>
|
|
<a class="header" href="#scalars" id="scalars"><h1>Scalars</h1></a>
|
|
<p>Scalars are the primitive types at the leaves of a GraphQL query: numbers,
|
|
strings, and booleans. You can create custom scalars to other primitive values,
|
|
but this often requires coordination with the client library intended to consume
|
|
the API you're building.</p>
|
|
<p>Since any value going over the wire is eventually transformed into JSON, you're
|
|
also limited in the data types you can use.</p>
|
|
<p>There are two ways to define custom scalars.</p>
|
|
<ul>
|
|
<li>For simple scalars that just wrap a primitive type, you can use the newtype pattern with
|
|
a custom derive.</li>
|
|
<li>For more advanced use cases with custom validation, you can use
|
|
the <code>graphql_scalar!</code> macro.</li>
|
|
</ul>
|
|
<a class="header" href="#built-in-scalars" id="built-in-scalars"><h2>Built-in scalars</h2></a>
|
|
<p>Juniper has built-in support for:</p>
|
|
<ul>
|
|
<li><code>i32</code> as <code>Int</code></li>
|
|
<li><code>f64</code> as <code>Float</code></li>
|
|
<li><code>String</code> and <code>&str</code> as <code>String</code></li>
|
|
<li><code>bool</code> as <code>Boolean</code></li>
|
|
<li><code>juniper::ID</code> as <code>ID</code>. This type is defined <a href="http://facebook.github.io/graphql/#sec-ID">in the
|
|
spec</a> as a type that is serialized
|
|
as a string but can be parsed from both a string and an integer.</li>
|
|
</ul>
|
|
<p><strong>Third party types</strong>:</p>
|
|
<p>Juniper has built-in support for a few additional types from common third party
|
|
crates. They are enabled via features that are on by default.</p>
|
|
<ul>
|
|
<li>uuid::Uuid</li>
|
|
<li>chrono::DateTime</li>
|
|
<li>url::Url</li>
|
|
</ul>
|
|
<a class="header" href="#newtype-pattern" id="newtype-pattern"><h2>newtype pattern</h2></a>
|
|
<p>Often, you might need a custom scalar that just wraps an existing type.</p>
|
|
<p>This can be done with the newtype pattern and a custom derive, similar to how
|
|
serde supports this pattern with <code>#[serde(transparent)]</code>.</p>
|
|
<pre><pre class="playpen"><code class="language-rust">#[derive(juniper::GraphQLScalarValue)]
|
|
pub struct UserId(i32);
|
|
|
|
#[derive(juniper::GraphQLObject)]
|
|
struct User {
|
|
id: UserId,
|
|
}
|
|
|
|
# fn main() {}
|
|
</code></pre></pre>
|
|
<p>That's it, you can now user <code>UserId</code> in your schema.</p>
|
|
<p>The macro also allows for more customization:</p>
|
|
<pre><pre class="playpen"><code class="language-rust">/// You can use a doc comment to specify a description.
|
|
#[derive(juniper::GraphQLScalarValue)]
|
|
#[graphql(
|
|
transparent,
|
|
// Overwrite the GraphQL type name.
|
|
name = "MyUserId",
|
|
// Specify a custom description.
|
|
// A description in the attribute will overwrite a doc comment.
|
|
description = "My user id description",
|
|
)]
|
|
pub struct UserId(i32);
|
|
|
|
# fn main() {}
|
|
</code></pre></pre>
|
|
<a class="header" href="#custom-scalars" id="custom-scalars"><h2>Custom scalars</h2></a>
|
|
<p>For more complex situations where you also need custom parsing or validation,
|
|
you can use the <code>graphql_scalar!</code> macro.</p>
|
|
<p>Typically, you represent your custom scalars as strings.</p>
|
|
<p>The example below implements a custom scalar for a custom <code>Date</code> type.</p>
|
|
<p>Note: juniper already has built-in support for the <code>chrono::DateTime</code> type
|
|
via <code>chrono</code> feature, which is enabled by default and should be used for this
|
|
purpose.</p>
|
|
<p>The example below is used just for illustration.</p>
|
|
<p><strong>Note</strong>: the example assumes that the <code>Date</code> type implements
|
|
<code>std::fmt::Display</code> and <code>std::str::FromStr</code>.</p>
|
|
<pre><pre class="playpen"><code class="language-rust"># mod date {
|
|
# pub struct Date;
|
|
# impl std::str::FromStr for Date{
|
|
# type Err = String; fn from_str(_value: &str) -> Result<Self, Self::Err> { unimplemented!() }
|
|
# }
|
|
# // And we define how to represent date as a string.
|
|
# impl std::fmt::Display for Date {
|
|
# fn fmt(&self, _f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
# unimplemented!()
|
|
# }
|
|
# }
|
|
# }
|
|
|
|
use juniper::{Value, ParseScalarResult, ParseScalarValue};
|
|
use date::Date;
|
|
|
|
juniper::graphql_scalar!(Date where Scalar = <S> {
|
|
description: "Date"
|
|
|
|
// Define how to convert your custom scalar into a primitive type.
|
|
resolve(&self) -> Value {
|
|
Value::scalar(self.to_string())
|
|
}
|
|
|
|
// Define how to parse a primitive type into your custom scalar.
|
|
from_input_value(v: &InputValue) -> Option<Date> {
|
|
v.as_scalar_value::<String>()
|
|
.and_then(|s| s.parse().ok())
|
|
}
|
|
|
|
// Define how to parse a string value.
|
|
from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
|
|
<String as ParseScalarValue<S>>::from_str(value)
|
|
}
|
|
});
|
|
|
|
# fn main() {}
|
|
</code></pre></pre>
|
|
<a class="header" href="#unions" id="unions"><h1>Unions</h1></a>
|
|
<p>From a server's point of view, GraphQL unions are similar to interfaces: the
|
|
only exception is that they don't contain fields on their own.</p>
|
|
<p>In Juniper, the <code>graphql_union!</code> has identical syntax to the <a href="interfaces.html">interface
|
|
macro</a>, but does not support defining fields. Therefore, the same
|
|
considerations about using traits, placeholder types, or enums still apply to
|
|
unions.</p>
|
|
<p>If we look at the same examples as in the interfaces chapter, we see the
|
|
similarities and the tradeoffs:</p>
|
|
<a class="header" href="#traits-1" id="traits-1"><h2>Traits</h2></a>
|
|
<a class="header" href="#downcasting-via-accessor-methods-1" id="downcasting-via-accessor-methods-1"><h3>Downcasting via accessor methods</h3></a>
|
|
<pre><pre class="playpen"><code class="language-rust">#[derive(juniper::GraphQLObject)]
|
|
struct Human {
|
|
id: String,
|
|
home_planet: String,
|
|
}
|
|
|
|
#[derive(juniper::GraphQLObject)]
|
|
struct Droid {
|
|
id: String,
|
|
primary_function: String,
|
|
}
|
|
|
|
trait Character {
|
|
// Downcast methods, each concrete class will need to implement one of these
|
|
fn as_human(&self) -> Option<&Human> { None }
|
|
fn as_droid(&self) -> Option<&Droid> { None }
|
|
}
|
|
|
|
impl Character for Human {
|
|
fn as_human(&self) -> Option<&Human> { Some(&self) }
|
|
}
|
|
|
|
impl Character for Droid {
|
|
fn as_droid(&self) -> Option<&Droid> { Some(&self) }
|
|
}
|
|
|
|
juniper::graphql_union!(<'a> &'a Character: () as "Character" where Scalar = <S> |&self| {
|
|
instance_resolvers: |_| {
|
|
// The left hand side indicates the concrete type T, the right hand
|
|
// side should be an expression returning Option<T>
|
|
&Human => self.as_human(),
|
|
&Droid => self.as_droid(),
|
|
}
|
|
});
|
|
|
|
# fn main() {}
|
|
</code></pre></pre>
|
|
<a class="header" href="#using-an-extra-database-lookup-1" id="using-an-extra-database-lookup-1"><h3>Using an extra database lookup</h3></a>
|
|
<p>FIXME: This example does not compile at the moment</p>
|
|
<pre><pre class="playpen"><code class="language-rust"># use std::collections::HashMap;
|
|
#[derive(juniper::GraphQLObject)]
|
|
#[graphql(Context = Database)]
|
|
struct Human {
|
|
id: String,
|
|
home_planet: String,
|
|
}
|
|
|
|
#[derive(juniper::GraphQLObject)]
|
|
#[graphql(Context = Database)]
|
|
struct Droid {
|
|
id: String,
|
|
primary_function: String,
|
|
}
|
|
|
|
struct Database {
|
|
humans: HashMap<String, Human>,
|
|
droids: HashMap<String, Droid>,
|
|
}
|
|
|
|
impl juniper::Context for Database {}
|
|
|
|
trait Character {
|
|
fn id(&self) -> &str;
|
|
}
|
|
|
|
impl Character for Human {
|
|
fn id(&self) -> &str { self.id.as_str() }
|
|
}
|
|
|
|
impl Character for Droid {
|
|
fn id(&self) -> &str { self.id.as_str() }
|
|
}
|
|
|
|
juniper::graphql_union!(<'a> &'a Character: Database as "Character" where Scalar = <S> |&self| {
|
|
instance_resolvers: |&context| {
|
|
&Human => context.humans.get(self.id()),
|
|
&Droid => context.droids.get(self.id()),
|
|
}
|
|
});
|
|
|
|
# fn main() {}
|
|
</code></pre></pre>
|
|
<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;
|
|
#[derive(juniper::GraphQLObject)]
|
|
#[graphql(Context = Database)]
|
|
struct Human {
|
|
id: String,
|
|
home_planet: String,
|
|
}
|
|
|
|
#[derive(juniper::GraphQLObject)]
|
|
#[graphql(Context = Database)]
|
|
struct Droid {
|
|
id: String,
|
|
primary_function: String,
|
|
}
|
|
|
|
struct Database {
|
|
humans: HashMap<String, Human>,
|
|
droids: HashMap<String, Droid>,
|
|
}
|
|
|
|
impl juniper::Context for Database {}
|
|
|
|
struct Character {
|
|
id: String,
|
|
}
|
|
|
|
juniper::graphql_union!(Character: Database where Scalar = <S> |&self| {
|
|
instance_resolvers: |&context| {
|
|
&Human => context.humans.get(&self.id),
|
|
&Droid => context.droids.get(&self.id),
|
|
}
|
|
});
|
|
|
|
# fn main() {}
|
|
</code></pre></pre>
|
|
<a class="header" href="#enums-2" id="enums-2"><h2>Enums</h2></a>
|
|
<pre><pre class="playpen"><code class="language-rust">#[derive(juniper::GraphQLObject)]
|
|
struct Human {
|
|
id: String,
|
|
home_planet: String,
|
|
}
|
|
|
|
#[derive(juniper::GraphQLObject)]
|
|
struct Droid {
|
|
id: String,
|
|
primary_function: String,
|
|
}
|
|
|
|
# #[allow(dead_code)]
|
|
enum Character {
|
|
Human(Human),
|
|
Droid(Droid),
|
|
}
|
|
|
|
juniper::graphql_union!(Character: () where Scalar = <S> |&self| {
|
|
instance_resolvers: |_| {
|
|
&Human => match *self { Character::Human(ref h) => Some(h), _ => None },
|
|
&Droid => match *self { Character::Droid(ref d) => Some(d), _ => None },
|
|
}
|
|
});
|
|
|
|
# fn main() {}
|
|
</code></pre></pre>
|
|
<a class="header" href="#schemas" id="schemas"><h1>Schemas</h1></a>
|
|
<p>A schema consists of two types: a query object and a mutation object (Juniper
|
|
does not support subscriptions yet). These two define the root query fields
|
|
and mutations of the schema, respectively.</p>
|
|
<p>Both query and mutation objects are regular GraphQL objects, defined like any
|
|
other object in Juniper. The mutation object, however, is optional since schemas
|
|
can be read-only.</p>
|
|
<p>In Juniper, the <code>RootNode</code> type represents a schema. You usually don't have to
|
|
create this object yourself: see the framework integrations for <a href="../servers/iron.html">Iron</a>
|
|
and <a href="../servers/rocket.html">Rocket</a> how schemas are created together with the handlers
|
|
themselves.</p>
|
|
<p>When the schema is first created, Juniper will traverse the entire object graph
|
|
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>
|
|
<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
|
|
object in Juniper, most commonly using the <code>object</code> proc macro:</p>
|
|
<pre><pre class="playpen"><code class="language-rust"># use juniper::FieldResult;
|
|
# #[derive(juniper::GraphQLObject)] struct User { name: String }
|
|
struct Root;
|
|
|
|
#[juniper::object]
|
|
impl Root {
|
|
fn userWithUsername(username: String) -> FieldResult<Option<User>> {
|
|
// Look up user in database...
|
|
# unimplemented!()
|
|
}
|
|
}
|
|
|
|
# fn main() { }
|
|
</code></pre></pre>
|
|
<a class="header" href="#mutations" id="mutations"><h2>Mutations</h2></a>
|
|
<p>Mutations are <em>also</em> just GraphQL objects. Each mutation is a single field that
|
|
usually performs some mutating side-effect, such as updating a database.</p>
|
|
<pre><pre class="playpen"><code class="language-rust"># use juniper::FieldResult;
|
|
# #[derive(juniper::GraphQLObject)] struct User { name: String }
|
|
struct Mutations;
|
|
|
|
#[juniper::object]
|
|
impl Mutations {
|
|
fn signUpUser(name: String, email: String) -> FieldResult<User> {
|
|
// Validate inputs and save user in database...
|
|
# unimplemented!()
|
|
}
|
|
}
|
|
|
|
# fn main() { }
|
|
</code></pre></pre>
|
|
<a class="header" href="#adding-a-server" id="adding-a-server"><h1>Adding A Server</h1></a>
|
|
<p>To allow using Juniper with the HTTP server of your choice,
|
|
it does <strong>not</strong> come with a built in HTTP server.</p>
|
|
<p>To actually get a server up and running, there are multiple official and
|
|
third-party integration crates that will get you there.</p>
|
|
<ul>
|
|
<li><a href="official.html">Official Server Integrations</a>
|
|
<ul>
|
|
<li><a href="hyper.html">Hyper</a></li>
|
|
<li><a href="warp.html">Warp</a></li>
|
|
<li><a href="rocket.html">Rocket</a></li>
|
|
<li><a href="iron.html">Iron</a></li>
|
|
<li><a href="hyper.html">Hyper</a></li>
|
|
</ul>
|
|
</li>
|
|
<li><a href="third-party.html">Third Party Integrations</a>
|
|
<ul>
|
|
<li><a href="https://github.com/actix/examples/tree/master/juniper">Actix-Web</a></li>
|
|
<li><a href="https://github.com/finchers-rs/finchers-juniper">Finchers</a></li>
|
|
<li><a href="https://github.com/tsukuyomi-rs/tsukuyomi/tree/master/examples/juniper">Tsukuyomi</a></li>
|
|
</ul>
|
|
</li>
|
|
</ul>
|
|
<a class="header" href="#official-server-integrations" id="official-server-integrations"><h1>Official Server Integrations</h1></a>
|
|
<p>Juniper provides official integration crates for several popular Rust server
|
|
libraries.</p>
|
|
<ul>
|
|
<li><a href="hyper.html">Hyper</a></li>
|
|
<li><a href="warp.html">Warp</a></li>
|
|
<li><a href="rocket.html">Rocket</a></li>
|
|
<li><a href="iron.html">Iron</a></li>
|
|
</ul>
|
|
<a class="header" href="#integrating-with-warp" id="integrating-with-warp"><h1>Integrating with Warp</h1></a>
|
|
<p><a href="https://crates.io/crates/warp">Warp</a> is a super-easy, composable, web server framework for warp speeds.
|
|
The fundamental building block of warp is the Filter: they can be combined and composed to express rich requirements on requests. Warp is built on <a href="https://hyper.rs/">Hyper</a> and works on
|
|
Rust's stable channel.</p>
|
|
<p>Juniper's Warp integration is contained in the <a href="https://github.com/graphql-rust/juniper/tree/master/juniper_warp"><code>juniper_warp</code></a> crate:</p>
|
|
<p>!FILENAME Cargo.toml</p>
|
|
<pre><code class="language-toml">[dependencies]
|
|
juniper = "0.10"
|
|
juniper_warp = "0.1.0"
|
|
</code></pre>
|
|
<p>Included in the source is a <a href="https://github.com/graphql-rust/juniper/blob/master/juniper_warp/examples/warp_server.rs">small example</a> which sets up a basic GraphQL and <a href="https://github.com/graphql/graphiql">GraphiQL</a> handler.</p>
|
|
<a class="header" href="#integrating-with-rocket" id="integrating-with-rocket"><h1>Integrating with Rocket</h1></a>
|
|
<p><a href="https://rocket.rs/">Rocket</a> is a web framework for Rust that makes it simple to write fast web applications without sacrificing flexibility or type safety. All with minimal code. Rocket
|
|
does not work on Rust's stable channel and instead requires the nightly
|
|
channel.</p>
|
|
<p>Juniper's Rocket integration is contained in the <a href="https://github.com/graphql-rust/juniper/tree/master/juniper_rocket"><code>juniper_rocket</code></a> crate:</p>
|
|
<p>!FILENAME Cargo.toml</p>
|
|
<pre><code class="language-toml">[dependencies]
|
|
juniper = "0.10"
|
|
juniper_rocket = "0.2.0"
|
|
</code></pre>
|
|
<p>Included in the source is a <a href="https://github.com/graphql-rust/juniper/blob/master/juniper_rocket/examples/rocket_server.rs">small example</a> which sets up a basic GraphQL and <a href="https://github.com/graphql/graphiql">GraphiQL</a> handler.</p>
|
|
<a class="header" href="#integrating-with-iron" id="integrating-with-iron"><h1>Integrating with Iron</h1></a>
|
|
<p><a href="http://ironframework.io">Iron</a> is a library that's been around for a while in the Rust sphere but lately
|
|
hasn't seen much of development. Nevertheless, it's still a solid library with a
|
|
familiar request/response/middleware architecture that works on Rust's stable
|
|
channel.</p>
|
|
<p>Juniper's Iron integration is contained in the <code>juniper_iron</code> crate:</p>
|
|
<p>!FILENAME Cargo.toml</p>
|
|
<pre><code class="language-toml">[dependencies]
|
|
juniper = "0.10"
|
|
juniper_iron = "0.2.0"
|
|
</code></pre>
|
|
<p>Included in the source is a <a href="https://github.com/graphql-rust/juniper_iron/blob/master/examples/iron_server.rs">small
|
|
example</a>
|
|
which sets up a basic GraphQL and <a href="https://github.com/graphql/graphiql">GraphiQL</a> handler.</p>
|
|
<a class="header" href="#basic-integration" id="basic-integration"><h2>Basic integration</h2></a>
|
|
<p>Let's start with a minimal schema and just get a GraphQL endpoint up and
|
|
running. We use <a href="https://github.com/iron/mount">mount</a> to attach the GraphQL handler at <code>/graphql</code>.</p>
|
|
<p>The <code>context_factory</code> function will be executed on every request and can be used
|
|
to set up database connections, read session token information from cookies, and
|
|
set up other global data that the schema might require.</p>
|
|
<p>In this example, we won't use any global data so we just return an empty value.</p>
|
|
<pre><code class="language-rust ignore">extern crate juniper;
|
|
extern crate juniper_iron;
|
|
extern crate iron;
|
|
extern crate mount;
|
|
|
|
use mount::Mount;
|
|
use iron::prelude::*;
|
|
use juniper::EmptyMutation;
|
|
use juniper_iron::GraphQLHandler;
|
|
|
|
fn context_factory(_: &mut Request) -> IronResult<()> {
|
|
Ok(())
|
|
}
|
|
|
|
struct Root;
|
|
|
|
#[juniper::object]
|
|
impl Root {
|
|
fn foo() -> String {
|
|
"Bar".to_owned()
|
|
}
|
|
}
|
|
|
|
# #[allow(unreachable_code, unused_variables)]
|
|
fn main() {
|
|
let mut mount = Mount::new();
|
|
|
|
let graphql_endpoint = GraphQLHandler::new(
|
|
context_factory,
|
|
Root,
|
|
EmptyMutation::<()>::new(),
|
|
);
|
|
|
|
mount.mount("/graphql", graphql_endpoint);
|
|
|
|
let chain = Chain::new(mount);
|
|
|
|
# return;
|
|
Iron::new(chain).http("0.0.0.0:8080").unwrap();
|
|
}
|
|
</code></pre>
|
|
<a class="header" href="#accessing-data-from-the-request" id="accessing-data-from-the-request"><h2>Accessing data from the request</h2></a>
|
|
<p>If you want to access e.g. the source IP address of the request from a field
|
|
resolver, you need to pass this data using Juniper's <a href="../types/objects/using_contexts.html">context feature</a>.</p>
|
|
<pre><code class="language-rust ignore"># extern crate juniper;
|
|
# extern crate juniper_iron;
|
|
# extern crate iron;
|
|
# use iron::prelude::*;
|
|
use std::net::SocketAddr;
|
|
|
|
struct Context {
|
|
remote_addr: SocketAddr,
|
|
}
|
|
|
|
impl juniper::Context for Context {}
|
|
|
|
fn context_factory(req: &mut Request) -> IronResult<Context> {
|
|
Ok(Context {
|
|
remote_addr: req.remote_addr
|
|
})
|
|
}
|
|
|
|
struct Root;
|
|
|
|
#[juniper::object(
|
|
Context = Context,
|
|
)]
|
|
impl Root {
|
|
field my_addr(context: &Context) -> String {
|
|
format!("Hello, you're coming from {}", context.remote_addr)
|
|
}
|
|
}
|
|
|
|
# fn main() {
|
|
# let _graphql_endpoint = juniper_iron::GraphQLHandler::new(
|
|
# context_factory,
|
|
# Root,
|
|
# juniper::EmptyMutation::<Context>::new(),
|
|
# );
|
|
# }
|
|
</code></pre>
|
|
<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
|
|
leverage. It offers asynchronous I/O via the tokio runtime and works on
|
|
Rust's stable channel.</p>
|
|
<p>Hyper is not a higher-level web framework and accordingly
|
|
does not include ergonomic features such as simple endpoint routing,
|
|
baked-in HTTP responses, or reusable middleware. For GraphQL, those aren't
|
|
large downsides as all POSTs and GETs usually go through a single endpoint with
|
|
a few clearly-defined response payloads.</p>
|
|
<p>Juniper's Hyper integration is contained in the <a href="https://github.com/graphql-rust/juniper/tree/master/juniper_hyper"><code>juniper_hyper</code></a> crate:</p>
|
|
<p>!FILENAME Cargo.toml</p>
|
|
<pre><code class="language-toml">[dependencies]
|
|
juniper = "0.10"
|
|
juniper_hyper = "0.1.0"
|
|
</code></pre>
|
|
<p>Included in the source is a <a href="https://github.com/graphql-rust/juniper/blob/master/juniper_hyper/examples/hyper_server.rs">small example</a> which sets up a basic GraphQL and <a href="https://github.com/graphql/graphiql">GraphiQL</a> handler.</p>
|
|
<a class="header" href="#third-party-integrations" id="third-party-integrations"><h1>Third-Party Integrations</h1></a>
|
|
<p>There are several examples or third party integration crates that are not
|
|
officially maintained by Juniper developers.</p>
|
|
<ul>
|
|
<li><a href="https://github.com/actix/examples/tree/master/juniper">Actix-Web</a></li>
|
|
<li><a href="https://github.com/finchers-rs/finchers-juniper">Finchers</a></li>
|
|
<li><a href="https://github.com/tsukuyomi-rs/tsukuyomi/tree/master/examples/juniper">Tsukuyomi</a></li>
|
|
</ul>
|
|
<a class="header" href="#advanced-topics" id="advanced-topics"><h1>Advanced Topics</h1></a>
|
|
<p>The chapters below cover some more advanced scenarios.</p>
|
|
<ul>
|
|
<li><a href="introspection.html">Introspection</a></li>
|
|
<li><a href="non_struct_objects.html">Non-struct objects</a></li>
|
|
<li><a href="objects_and_generics.html">Objects and generics</a></li>
|
|
<li><a href="multiple_ops_per_request.html">Multiple operations per request</a></li>
|
|
</ul>
|
|
<a class="header" href="#introspection" id="introspection"><h1>Introspection</h1></a>
|
|
<p>GraphQL defines a special built-in top-level field called <code>__schema</code>. Querying
|
|
for this field allows one to <a href="https://graphql.org/learn/introspection/">introspect the schema</a>
|
|
at runtime to see what queries and mutations the GraphQL server supports.</p>
|
|
<p>Because introspection queries are just regular GraphQL queries, Juniper supports
|
|
them natively. For example, to get all the names of the types supported one
|
|
could execute the following query against Juniper:</p>
|
|
<pre><code class="language-graphql">{
|
|
__schema {
|
|
types {
|
|
name
|
|
}
|
|
}
|
|
}
|
|
</code></pre>
|
|
<a class="header" href="#schema-introspection-output-as-json" id="schema-introspection-output-as-json"><h2>Schema introspection output as JSON</h2></a>
|
|
<p>Many client libraries and tools in the GraphQL ecosystem require a complete
|
|
representation of the server schema. Often this representation is in JSON and
|
|
referred to as <code>schema.json</code>. A complete representation of the schema can be
|
|
produced by issuing a specially crafted introspection query.</p>
|
|
<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
|
|
<a href="https://github.com/graphql-rust/graphql-client">graphql-client</a>:</p>
|
|
<pre><pre class="playpen"><code class="language-rust">use juniper::{EmptyMutation, FieldResult, IntrospectionFormat};
|
|
|
|
// Define our schema.
|
|
|
|
#[derive(juniper::GraphQLObject)]
|
|
struct Example {
|
|
id: String,
|
|
}
|
|
|
|
struct Context;
|
|
impl juniper::Context for Context {}
|
|
|
|
struct Query;
|
|
|
|
#[juniper::object(
|
|
Context = Context,
|
|
)]
|
|
impl Query {
|
|
fn example(id: String) -> FieldResult<Example> {
|
|
unimplemented!()
|
|
}
|
|
}
|
|
|
|
type Schema = juniper::RootNode<'static, Query, EmptyMutation<Context>>;
|
|
|
|
fn main() {
|
|
// Create a context object.
|
|
let ctx = Context{};
|
|
|
|
// Run the built-in introspection query.
|
|
let (res, _errors) = juniper::introspect(
|
|
&Schema::new(Query, EmptyMutation::new()),
|
|
&ctx,
|
|
IntrospectionFormat::default(),
|
|
).unwrap();
|
|
|
|
// Convert introspection result to json.
|
|
let json_result = serde_json::to_string_pretty(&res);
|
|
assert!(json_result.is_ok());
|
|
}
|
|
</code></pre></pre>
|
|
<a class="header" href="#non-struct-objects" id="non-struct-objects"><h1>Non-struct objects</h1></a>
|
|
<p>Up until now, we've only looked at mapping structs to GraphQL objects. However,
|
|
any Rust type can be mapped into a GraphQL object. In this chapter, we'll look
|
|
at enums, but traits will work too - they don't <em>have</em> to be mapped into GraphQL
|
|
interfaces.</p>
|
|
<p>Using <code>Result</code>-like enums can be a useful way of reporting e.g. validation
|
|
errors from a mutation:</p>
|
|
<pre><pre class="playpen"><code class="language-rust"># #[derive(juniper::GraphQLObject)] struct User { name: String }
|
|
|
|
#[derive(juniper::GraphQLObject)]
|
|
struct ValidationError {
|
|
field: String,
|
|
message: String,
|
|
}
|
|
|
|
# #[allow(dead_code)]
|
|
enum SignUpResult {
|
|
Ok(User),
|
|
Error(Vec<ValidationError>),
|
|
}
|
|
|
|
#[juniper::object]
|
|
impl SignUpResult {
|
|
fn user(&self) -> Option<&User> {
|
|
match *self {
|
|
SignUpResult::Ok(ref user) => Some(user),
|
|
SignUpResult::Error(_) => None,
|
|
}
|
|
}
|
|
|
|
fn error(&self) -> Option<&Vec<ValidationError>> {
|
|
match *self {
|
|
SignUpResult::Ok(_) => None,
|
|
SignUpResult::Error(ref errors) => Some(errors)
|
|
}
|
|
}
|
|
}
|
|
|
|
# fn main() {}
|
|
</code></pre></pre>
|
|
<p>Here, we use an enum to decide whether a user's input data was valid or not, and
|
|
it could be used as the result of e.g. a sign up mutation.</p>
|
|
<p>While this is an example of how you could use something other than a struct to
|
|
represent a GraphQL object, it's also an example on how you could implement
|
|
error handling for "expected" errors - errors like validation errors. There are
|
|
no hard rules on how to represent errors in GraphQL, but there are
|
|
<a href="https://github.com/facebook/graphql/issues/117#issuecomment-170180628">some</a>
|
|
<a href="https://github.com/graphql/graphql-js/issues/560#issuecomment-259508214">comments</a>
|
|
from one of the authors of GraphQL on how they intended "hard" field errors to
|
|
be used, and how to model expected errors.</p>
|
|
<a class="header" href="#objects-and-generics" id="objects-and-generics"><h1>Objects and generics</h1></a>
|
|
<p>Yet another point where GraphQL and Rust differs is in how generics work. In
|
|
Rust, almost any type could be generic - that is, take type parameters. In
|
|
GraphQL, there are only two generic types: lists and non-nullables.</p>
|
|
<p>This poses a restriction on what you can expose in GraphQL from Rust: no generic
|
|
structs can be exposed - all type parameters must be bound. For example, you can
|
|
not make e.g. <code>Result<T, E></code> into a GraphQL type, but you <em>can</em> make e.g.
|
|
<code>Result<User, String></code> into a GraphQL type.</p>
|
|
<p>Let's make a slightly more compact but generic implementation of <a href="non_struct_objects.html">the last
|
|
chapter</a>:</p>
|
|
<pre><pre class="playpen"><code class="language-rust"># #[derive(juniper::GraphQLObject)] struct User { name: String }
|
|
# #[derive(juniper::GraphQLObject)] struct ForumPost { title: String }
|
|
|
|
#[derive(juniper::GraphQLObject)]
|
|
struct ValidationError {
|
|
field: String,
|
|
message: String,
|
|
}
|
|
|
|
# #[allow(dead_code)]
|
|
struct MutationResult<T>(Result<T, Vec<ValidationError>>);
|
|
|
|
#[juniper::object(
|
|
name = "UserResult",
|
|
)]
|
|
impl MutationResult<User> {
|
|
fn user(&self) -> Option<&User> {
|
|
self.0.as_ref().ok()
|
|
}
|
|
|
|
fn error(&self) -> Option<&Vec<ValidationError>> {
|
|
self.0.as_ref().err()
|
|
}
|
|
}
|
|
|
|
#[juniper::object(
|
|
name = "ForumPostResult",
|
|
)]
|
|
impl MutationResult<ForumPost> {
|
|
fn forum_post(&self) -> Option<&ForumPost> {
|
|
self.0.as_ref().ok()
|
|
}
|
|
|
|
fn error(&self) -> Option<&Vec<ValidationError>> {
|
|
self.0.as_ref().err()
|
|
}
|
|
}
|
|
|
|
# fn main() {}
|
|
</code></pre></pre>
|
|
<p>Here, we've made a wrapper around <code>Result</code> and exposed some concrete
|
|
instantiations of <code>Result<T, E></code> as distinct GraphQL objects. The reason we
|
|
needed the wrapper is of Rust's rules for when you can derive a trait - in this
|
|
case, both <code>Result</code> and Juniper's internal GraphQL trait are from third-party
|
|
sources.</p>
|
|
<p>Because we're using generics, we also need to specify a name for our
|
|
instantiated types. Even if Juniper <em>could</em> figure out the name,
|
|
<code>MutationResult<User></code> wouldn't be a valid GraphQL type name.</p>
|
|
<a class="header" href="#multiple-operations-per-request" id="multiple-operations-per-request"><h1>Multiple operations per request</h1></a>
|
|
<p>The GraphQL standard generally assumes there will be one server request for each client operation you want to perform (such as a query or mutation). This is conceptually simple but has the potential to be inefficent.</p>
|
|
<p>Some client libraries such as <a href="https://www.apollographql.com/docs/link/links/batch-http.html">apollo-link-batch-http</a> have added the ability to batch operations in a single HTTP request to save network round-trips and potentially increase performance. There are some <a href="https://blog.apollographql.com/batching-client-graphql-queries-a685f5bcd41b">tradeoffs</a> that should be considered before batching requests.</p>
|
|
<p>Juniper's server integration crates support multiple operations in a single HTTP request using JSON arrays. This makes them compatible with client libraries that support batch operations without any special configuration.</p>
|
|
<p>Server integration crates maintained by others are <strong>not required</strong> to support batch requests. Batch requests aren't part of the official GraphQL specification.</p>
|
|
<p>Assuming an integration supports batch requests, for the following GraphQL query:</p>
|
|
<pre><code class="language-graphql">{
|
|
hero {
|
|
name
|
|
}
|
|
}
|
|
</code></pre>
|
|
<p>The json data to POST to the server for an individual request would be:</p>
|
|
<pre><code class="language-json">{
|
|
"query": "{hero{name}}"
|
|
}
|
|
</code></pre>
|
|
<p>And the response would be of the form:</p>
|
|
<pre><code class="language-json">{
|
|
"data": {
|
|
"hero": {
|
|
"name": "R2-D2"
|
|
}
|
|
}
|
|
}
|
|
</code></pre>
|
|
<p>If you wanted to run the same query twice in a single HTTP request, the batched json data to POST to the server would be:</p>
|
|
<pre><code class="language-json">[
|
|
{
|
|
"query": "{hero{name}}"
|
|
},
|
|
{
|
|
"query": "{hero{name}}"
|
|
}
|
|
]
|
|
</code></pre>
|
|
<p>And the response would be of the form:</p>
|
|
<pre><code class="language-json">[
|
|
{
|
|
"data": {
|
|
"hero": {
|
|
"name": "R2-D2"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"data": {
|
|
"hero": {
|
|
"name": "R2-D2"
|
|
}
|
|
}
|
|
}
|
|
]
|
|
</code></pre>
|
|
|
|
</main>
|
|
|
|
<nav class="nav-wrapper" aria-label="Page navigation">
|
|
<!-- Mobile navigation buttons -->
|
|
|
|
|
|
|
|
|
|
<div style="clear: both"></div>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
|
|
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
|
|
|
|
|
|
|
</nav>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<script src="elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
|
|
<script src="mark.min.js" type="text/javascript" charset="utf-8"></script>
|
|
<script src="searcher.js" type="text/javascript" charset="utf-8"></script>
|
|
|
|
|
|
<script src="clipboard.min.js" type="text/javascript" charset="utf-8"></script>
|
|
<script src="highlight.js" type="text/javascript" charset="utf-8"></script>
|
|
<script src="book.js" type="text/javascript" charset="utf-8"></script>
|
|
|
|
<!-- Custom JS scripts -->
|
|
|
|
|
|
|
|
|
|
<script type="text/javascript">
|
|
window.addEventListener('load', function() {
|
|
window.setTimeout(window.print, 100);
|
|
});
|
|
</script>
|
|
|
|
|
|
|
|
</body>
|
|
</html>
|