warpgrapher 0.10.0

Automate web service creation with GraphQL and Graph Databases
Documentation
<!DOCTYPE HTML>
<html lang="en" class="sidebar-visible no-js light">
    <head>
        <!-- Book generated using mdBook -->
        <meta charset="UTF-8">
        <title>Warpgrapher Book (0.10.0)</title>
        <meta name="robots" content="noindex" />


        <!-- Custom HTML head -->
        
        <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
        <meta name="description" content="">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta name="theme-color" content="#ffffff" />

        <link rel="icon" href="favicon.svg">
        <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 rel="stylesheet" href="fonts/fonts.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>
        <!-- Provide site root to javascript -->
        <script type="text/javascript">
            var path_to_root = "";
            var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
        </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 = default_theme; }
            var html = document.querySelector('html');
            html.classList.remove('no-js')
            html.classList.remove('light')
            html.classList.add(theme);
            html.classList.add('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">
            <div class="sidebar-scrollbox">
                <ol class="chapter"><li class="chapter-item expanded "><a href="warpgrapher/intro.html"><strong aria-hidden="true">1.</strong> Warpgrapher</a></li><li class="chapter-item expanded "><a href="warpgrapher/quickstart.html"><strong aria-hidden="true">2.</strong> Quickstart</a></li><li class="chapter-item expanded "><a href="integrations/intro.html"><strong aria-hidden="true">3.</strong> Server Integration</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="integrations/actix.html"><strong aria-hidden="true">3.1.</strong> Actix Web</a></li><li class="chapter-item expanded "><a href="integrations/lambda.html"><strong aria-hidden="true">3.2.</strong> AWS Lambda</a></li></ol></li><li class="chapter-item expanded "><a href="configuration/intro.html"><strong aria-hidden="true">4.</strong> Configuration</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="configuration/databases.html"><strong aria-hidden="true">4.1.</strong> Databases</a></li><li class="chapter-item expanded "><a href="configuration/formats.html"><strong aria-hidden="true">4.2.</strong> Formats</a></li><li class="chapter-item expanded "><a href="configuration/types.html"><strong aria-hidden="true">4.3.</strong> Types</a></li><li class="chapter-item expanded "><a href="configuration/relationships.html"><strong aria-hidden="true">4.4.</strong> Relationships</a></li></ol></li><li class="chapter-item expanded "><a href="api/intro.html"><strong aria-hidden="true">5.</strong> CRUD API Usage Examples</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="api/node_create.html"><strong aria-hidden="true">5.1.</strong> Node Create</a></li><li class="chapter-item expanded "><a href="api/node_read.html"><strong aria-hidden="true">5.2.</strong> Node Read</a></li><li class="chapter-item expanded "><a href="api/node_update.html"><strong aria-hidden="true">5.3.</strong> Node Update</a></li><li class="chapter-item expanded "><a href="api/node_delete.html"><strong aria-hidden="true">5.4.</strong> Node Delete</a></li><li class="chapter-item expanded "><a href="api/rel_create.html"><strong aria-hidden="true">5.5.</strong> Relationship Create</a></li><li class="chapter-item expanded "><a href="api/rel_read.html"><strong aria-hidden="true">5.6.</strong> Relationship Read</a></li><li class="chapter-item expanded "><a href="api/rel_update.html"><strong aria-hidden="true">5.7.</strong> Relationship Update</a></li><li class="chapter-item expanded "><a href="api/rel_delete.html"><strong aria-hidden="true">5.8.</strong> Relationship Delete</a></li></ol></li><li class="chapter-item expanded "><a href="engine/intro.html"><strong aria-hidden="true">6.</strong> Engine Features</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="engine/endpoints_static.html"><strong aria-hidden="true">6.1.</strong> Static Endpoints</a></li><li class="chapter-item expanded "><a href="engine/endpoints_defined.html"><strong aria-hidden="true">6.2.</strong> Defined Endpoints</a></li><li class="chapter-item expanded "><a href="engine/dynamic_props.html"><strong aria-hidden="true">6.3.</strong> Dynamic Props</a></li><li class="chapter-item expanded "><a href="engine/dynamic_rels.html"><strong aria-hidden="true">6.4.</strong> Dynamic Relationships</a></li><li class="chapter-item expanded "><a href="engine/context_request.html"><strong aria-hidden="true">6.5.</strong> Request Context</a></li><li class="chapter-item expanded "><a href="engine/input_validation.html"><strong aria-hidden="true">6.6.</strong> Input Validation</a></li><li class="chapter-item expanded "><a href="engine/event_handlers.html"><strong aria-hidden="true">6.7.</strong> Event Handlers</a></li></ol></li></ol>
            </div>
            <div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
        </nav>

        <div id="page-wrapper" class="page-wrapper">

            <div class="page">
                                <div id="menu-bar-hover-placeholder"></div>
                <div id="menu-bar" class="menu-bar sticky bordered">
                    <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 (default)</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">Warpgrapher Book (0.10.0)</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 id="search-wrapper" class="hidden">
                    <form id="searchbar-outer" class="searchbar-outer">
                        <input type="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>
                        <h1 id="warpgrapher"><a class="header" href="#warpgrapher">Warpgrapher</a></h1>
<p>Warpgrapher is framework for developing graph-based API services. Describe the 
data model for which you want to run a web service. Wargrapher automatically 
generates a GraphQL schema from the data model, as well as a set of resolvers 
for basic reate, read, update, and delete (CRUD) operations on that data.</p>
<p>If you need more more sophisticated, custom queries and endpoints, you can
supply your own custom resolvers. Warpgrapher will automatically generate the
GraphQL configuration and invoke your custom resolvers when appropriate.</p>
<p>The project is currently in development. Prior to reaching v1.0.0:</p>
<ol>
<li>Minor versions represent breaking changes.</li>
<li>Patch versions represent fixes and features.</li>
<li>There are no deprecation warnings between releases.</li>
</ol>
<p>For in-depth usage information, see the <a href="https://docs.rs/warpgrapher/latest/warpgrapher/">API Documentation</a>.</p>
<p>To browse the source code or contribute, see the project's <a href="https://github.com/warpforge/warpgrapher">GitHub Repository</a>.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="quickstart"><a class="header" href="#quickstart">Quickstart</a></h1>
<p>This guide will walk through creating a brand new project using the Warpgrapher engine. The quickstart example will create a very simple service. It will store email addresses for users. Warpgrapher is far more capable, allowing storage and retrieval of complex data models with many relationships. But for now, to get started quickly, begin with as simple a data model as possible.</p>
<p>This quickstart assumes a working knowledge of Rust, GraphQL, and at least one graph database. For example, we don't cover creating a new Rust project using <code>cargo init</code>.</p>
<h2 id="configuration"><a class="header" href="#configuration">Configuration</a></h2>
<p>First, set up the <code>Cargo.toml</code> file to import Warpgrapher as a dependency. There are crate features for each of the databases supported as a back-end.  Use the <code>gremlin</code> feature to support Gremlin-based databases such as Apache Tinkerpop and Azure CosmosDB. Use <code>cypher</code> to support Cypher-based databases, such as AWS Neptune and Neo4J. This tutorial example uses Neo4J.</p>
<p><code>Cargo.toml</code></p>
<pre><code class="language-toml">[dependencies]
warpgrapher = { version = &quot;0.10.0&quot;, features = [&quot;cypher&quot;] }
</code></pre>
<p>The <code>src/main.rs</code> file begins with a definition of the data model for the example:</p>
<pre><code class="language-rust no_run noplayground">static CONFIG: &amp;str = &quot;
version: 1
model:
  - name: User
    props:
      - name: email
        type: String
        required: false
&quot;;
</code></pre>
<p>Configurations are written in YAML. Although this example uses a static string for convenience, configurations may be stored in standalone files, or assembled from multiple parts.</p>
<p>The example configuration illustrates several principles in configuring a Warpgrapher engine. The configuration format itself is versioned, for backward compatibility. The <code>version: 1</code> line notes that this configuration uses version 1 of the configuration file format.  Until Warpgrapher reaches version 1.0, breaking changes in the config file format are permitted. After 1.0, breaking changes will trigger an increment to the configuration version.</p>
<p>The configuration contains a <code>model</code> object. The model is a list of types present in the data model. In this case, the data model has only a single type called <code>User</code>. Type definitions contain one or more properties on the type, listed under <code>props</code>. In this example, the <code>props</code> list contains only one property, named <code>email</code>. The <code>email</code> property is of type <code>String</code>.</p>
<p>Altogether, this configuration defines a very simple data model. That data model keeps records about users, and the one property tracked for users is their email address.</p>
<h2 id="source-code"><a class="header" href="#source-code">Source Code</a></h2>
<p>Once the configuration describing the data model is in place, it takes relatively little code to get a Warpgrapher engine up and running, ready to handle all the basic CRUD operations for that data.</p>
<p>The example creates a request context for the engine. The request context does two things. First, it tells the engine which type of database endpoint to use, which is Neo4J in this case. Second, the context provides a way for systems built on Warpgrapher to pass application-specific data into the engine for later use by custom-written endpoints and resolvers. In this example, there's no such custom data, so the context is empty other than designating a <code>DBEndpointType</code> of <code>CypherEndpoint</code>.</p>
<pre><code class="language-rust no_run noplayground">struct AppRequestContext {}

impl RequestContext for AppRequestContext {
    type DBEndpointType = CypherEndpoint;
    fn new() -&gt; AppRequestContext {
        AppRequestContext {}
    }
}
</code></pre>
<p>The Warpgrapher engine is asynchronous, so the main function is set up to be executed by Tokio in this example.</p>
<pre><code class="language-rust no_run noplayground">#[tokio::main]
async fn main() {
</code></pre>
<p>Warpgrapher is invoked to parse the configuration string created in <code>CONFIG</code> above.</p>
<pre><code class="language-rust no_run noplayground">    // parse warpgrapher config
    let config = Configuration::try_from(CONFIG.to_string()).expect(&quot;Failed to parse CONFIG&quot;);
</code></pre>
<p>Next, the databse endpoint is configured using a set of environment variables. See below for the correct environment variables and values.</p>
<pre><code class="language-rust no_run noplayground">    // define database endpoint
    let db = CypherEndpoint::from_env()
        .expect(&quot;Failed to parse cypher endpoint from environment&quot;)
        .pool()
        .await
        .expect(&quot;Failed to create cypher database pool&quot;);
</code></pre>
<p>The configuration and database created above are passed to the Warpgrapher engine, as follows.</p>
<pre><code class="language-rust no_run noplayground">    // create warpgrapher engine
    let engine: Engine&lt;AppRequestContext&gt; = Engine::new(config, db)
        .build()
        .expect(&quot;Failed to build engine&quot;);
</code></pre>
<p>At this point, the Warpgrapher engine is created and ready to field queries. The remainder of the source code in the example file created a simple query to demonstrate that the query engine is functioning.  It creates a sample GraphQL query, submits the query to the Warpgrapher engine, and then prints out the query results to stdout. In a realistic system, the Warpgrapher engine would be invoked from the handler function of an HTTP server.</p>
<pre><code class="language-rust no_run noplayground">    // execute graphql mutation to create new user
    let query = &quot;
        mutation {
            UserCreate(input: {
                email: \&quot;a@b.com\&quot;
            }) {
                id
                email
            }
        }
    &quot;
    .to_string();
    let metadata = HashMap::new();
    let result = engine.execute(query, None, metadata).await.unwrap();

    // display result
    println!(&quot;result: {:#?}&quot;, result);
</code></pre>
<h2 id="database"><a class="header" href="#database">Database</a></h2>
<p>Configure database settings using the following environment variables:</p>
<pre><code class="language-bash">export WG_CYPHER_HOST=127.0.0.1
export WG_CYPHER_PORT=7687
export WG_CYPHER_USER=neo4j
export WG_CYPHER_PASS=*MY-DB-PASSWORD*
</code></pre>
<p>Start a 4.1 Neo4j database:</p>
<pre><code class="language-bash">docker run --rm -p 7687:7687 -e NEO4J_AUTH=&quot;${WG_CYPHER_USER}/${WG_CYPHER_PASS}&quot; neo4j:4.4
</code></pre>
<h2 id="run"><a class="header" href="#run">Run</a></h2>
<p>Run the example using <code>cargo</code> as follows.</p>
<pre><code class="language-bash">cargo run
</code></pre>
<p>The output from the example should look something like the following.</p>
<pre><code>result: Object({
    &quot;data&quot;: Object({
        &quot;UserCreate&quot;: Object({
            &quot;id&quot;: String(
                &quot;7e1e3497-dcfd-4579-b690-86b110c8f96a&quot;,
            ),
            &quot;email&quot;: String(
                &quot;a@b.com&quot;,
            ),
        }),
    }),
})
</code></pre>
<p>The identifier will be a different UUID than the one shown above, of course.</p>
<h2 id="full-example-code"><a class="header" href="#full-example-code">Full Example Code</a></h2>
<p>The full example source code listing is below:</p>
<p><code>src/main.rs</code></p>
<pre><code class="language-rust no_run noplayground">use std::collections::HashMap;
use std::convert::TryFrom;
use warpgrapher::engine::config::Configuration;
use warpgrapher::engine::context::RequestContext;
use warpgrapher::engine::database::cypher::CypherEndpoint;
use warpgrapher::engine::database::DatabaseEndpoint;
use warpgrapher::Engine;

static CONFIG: &amp;str = &quot;
version: 1
model:
  - name: User
    props:
      - name: email
        type: String
        required: false
&quot;;

#[derive(Clone, Debug)]
struct AppRequestContext {}

impl RequestContext for AppRequestContext {
    type DBEndpointType = CypherEndpoint;
    fn new() -&gt; AppRequestContext {
        AppRequestContext {}
    }
}

#[tokio::main]
async fn main() {
    // parse warpgrapher config
    let config = Configuration::try_from(CONFIG.to_string()).expect(&quot;Failed to parse CONFIG&quot;);

    // define database endpoint
    let db = CypherEndpoint::from_env()
        .expect(&quot;Failed to parse cypher endpoint from environment&quot;)
        .pool()
        .await
        .expect(&quot;Failed to create cypher database pool&quot;);

    // create warpgrapher engine
    let engine: Engine&lt;AppRequestContext&gt; = Engine::new(config, db)
        .build()
        .expect(&quot;Failed to build engine&quot;);

    // execute graphql mutation to create new user
    let query = &quot;
        mutation {
            UserCreate(input: {
                email: \&quot;a@b.com\&quot;
            }) {
                id
                email
            }
        }
    &quot;
    .to_string();
    let metadata = HashMap::new();
    let result = engine.execute(query, None, metadata).await.unwrap();

    // display result
    println!(&quot;result: {:#?}&quot;, result);
}
</code></pre>
<div style="break-before: page; page-break-before: always;"></div><h1 id="server-integration"><a class="header" href="#server-integration">Server Integration</a></h1>
<p>The Warpgrapher engine does not come with a bundled HTTP server. Instead, it can be integrated with the HTTP server framework of choice for a given application. Or, instead, it can be invoked in other ways, such as from a lambda function.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="actix-web-integration"><a class="header" href="#actix-web-integration">Actix Web Integration</a></h1>
<p>A full example of integrating Warpgrapher with Actix Web is contained in the <a href="https://github.com/warpforge/warpgrapher-actixweb">warpgrapher-actixweb</a> respository on Github. A slightly simplified version of that project is reproduced with additional description below.</p>
<p>To integrate Warpgrapher with an Actix Web engine, include the following dependencies in the <code>Cargo.toml</code> file.</p>
<p><code>Cargo.toml</code></p>
<pre><code>[dependencies]
actix-http = &quot;3.0.0-beta.5&quot;
actix-web = &quot;4.0.0-beta.6&quot;
actix-cors = &quot;0.6.0-beta.2&quot;
serde = &quot;1.0.135&quot;
serde_json = &quot;1.0.78&quot;
warpgrapher = { version=&quot;0.10.0&quot;, features=[&quot;cypher&quot;]}
</code></pre>
<p>The rest of the code necessary to accomplish the integration is contained within the single source code file below. First, a number of structs and functions are imported from the Actix and Warpgrapher crates.</p>
<p><code>src/main.rs</code></p>
<pre><code>use actix_cors::Cors;
use actix_http::error::Error;
use actix_web::middleware::Logger;
use actix_web::web::{Data, Json};
use actix_web::{web, App, HttpResponse, HttpServer, Responder};
use serde::Deserialize;
use serde_json::Value;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::fs::File;

use warpgrapher::engine::config::Configuration;
use warpgrapher::engine::context::RequestContext;
use warpgrapher::engine::database::cypher::CypherEndpoint;
use warpgrapher::engine::database::DatabaseEndpoint;
use warpgrapher::juniper::http::playground::playground_source;
use warpgrapher::Engine;
</code></pre>
<p>The <code>AppData</code> struct, defined below, is used to pass application data created during setup into the web server. In the case of this integration, the application data that is passed into the web server is the Warpgrapher <code>Engine</code>.</p>
<pre><code>#[derive(Clone)]
struct AppData {
    engine: Engine&lt;Rctx&gt;,
}

impl AppData {
    fn new(engine: Engine&lt;Rctx&gt;) -&gt; AppData {
        AppData { engine }
    }
}
</code></pre>
<p>Just like the <a href="integrations/../warpgrapher/quickstart.html">Quickstart</a> tutorial, this integration creates a <code>RequestContext</code> that could be used to pass data into the Warpgrapher engine for custom resolvers or endpoints, but is left empty in this example. The <code>Rctx</code> struct does contain on associated type, which selects Cypher as the database type to be used for this Warpgrapher engine.</p>
<pre><code>#[derive(Clone, Debug)]
struct Rctx {}

impl RequestContext for Rctx {
    type DBEndpointType = CypherEndpoint;

    fn new() -&gt; Self {
        Rctx {}
    }
}
</code></pre>
<p>Next, the integration includes a <code>GraphqlRequest</code> that is used to deserialize queries coming from Actix Web and pass the query content to the Warpgrapher engine.</p>
<pre><code>#[derive(Clone, Debug, Deserialize)]
struct GraphqlRequest {
    pub query: String,
    pub variables: Option&lt;Value&gt;,
}
</code></pre>
<p>The following function is the handler that takes requests from the Actix Web framework and passes it into the Warpgrapher engine. In short, it pulls the query and query variables from the Actix Web query and passes those as arguments to the Warpgrapher engine's <code>execute</code> function. A successful response is passed back as an <code>Ok</code> result. Errors are returned within an InternalServerError.</p>
<pre><code>async fn graphql(data: Data&lt;AppData&gt;, req: Json&lt;GraphqlRequest&gt;) -&gt; Result&lt;HttpResponse, Error&gt; {
    let engine = &amp;data.engine;
    let metadata: HashMap&lt;String, String&gt; = HashMap::new();
    let resp = engine
        .execute(req.query.to_string(), req.variables.clone(), metadata)
        .await;
    match resp {
        Ok(body) =&gt; Ok(HttpResponse::Ok()
            .content_type(&quot;application/json&quot;)
            .body(body.to_string())),
        Err(e) =&gt; Ok(HttpResponse::InternalServerError()
            .content_type(&quot;application/json&quot;)
            .body(e.to_string())),
    }
}
</code></pre>
<p>To make it easier to explore the schema generated by Warpgrapher, the integration example also includes a handler function that returns a GraphQL playground, as the <code>/playground</code> path. The handler function is shown below.</p>
<pre><code>async fn playground(_data: Data&lt;AppData&gt;) -&gt; impl Responder {
    let html = playground_source(&quot;/graphql&quot;, None);
    HttpResponse::Ok()
        .content_type(&quot;text/html; charset=utf-8&quot;)
        .body(html)
}
</code></pre>
<p>The <code>create_engine</code> function pulls data from environment variables to determine how to connect to a Cypher-based database. These are the same environment variables described in the <a href="integrations/../warpgrapher/quickstart.html">Quickstart</a> and the Neo4J section of the <a href="integrations/../configuration/databases.html">Databases</a> book.</p>
<pre><code>async fn create_engine(config: Configuration) -&gt; Engine&lt;Rctx&gt; {
    let db = CypherEndpoint::from_env()
        .expect(&quot;Failed to parse endpoint from environment&quot;)
        .pool()
        .await
        .expect(&quot;Failed to create db endpoint&quot;);
    let engine: Engine&lt;Rctx&gt; = Engine::&lt;Rctx&gt;::new(config, db)
        .build()
        .expect(&quot;Failed to build engine&quot;);
    engine
}
</code></pre>
<p>Lastly, the main function itself pulls all of the above elements together. It reads a configuration from a <code>./config.yaml</code> file and passes that to the function defined above to create an Warpgrapher <code>Engine</code>. It packages the Warpgrapher engine into an <code>AppData</code> struct to pass off to Actix Web and creates an <code>HttpServer</code> to begin fielding requests. The GraphQL API is bound to the <code>/graphql</code> path, and the playground is bound to the <code>/playground</code> path.</p>
<pre><code>#[actix_web::main]
async fn main() -&gt; std::io::Result&lt;()&gt; {
    let config_file = File::open(&quot;./config.yaml&quot;.to_string()).expect(&quot;Could not read file&quot;);
    let config = Configuration::try_from(config_file).expect(&quot;Failed to parse config file&quot;);

    let engine = create_engine(config.clone()).await;

    let graphql_endpoint = &quot;/graphql&quot;;
    let playground_endpoint = &quot;/playground&quot;;
    let bind_addr = &quot;0.0.0.0&quot;.to_string();
    let bind_port = &quot;5000&quot;.to_string();
    let addr = format!(&quot;{}:{}&quot;, bind_addr, bind_port);

    let app_data = AppData::new(engine);

    println!(&quot;Starting server on {}&quot;, addr);
    HttpServer::new(move || {
        App::new()
            .app_data(actix_web::web::Data::new(app_data.clone()))
            .wrap(Logger::default())
            .wrap(Cors::permissive())
            .route(graphql_endpoint, web::post().to(graphql))
            .route(playground_endpoint, web::get().to(playground))
    })
    .bind(&amp;addr)
    .expect(&quot;Failed to start server&quot;)
    .run()
    .await
}
</code></pre>
<p>To view or clone a full repository project with an Actix Web integration, visit the <a href="https://github.com/warpforge/warpgrapher-actixweb">warpgrapher-actixweb</a> repository on GitHub.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="aws-lambda"><a class="header" href="#aws-lambda">AWS Lambda</a></h1>
<p>To integrate Warpgrapher with AWS lambda, begin by including the following dependencies in <code>Cargo.toml</code>. </p>
<p><code>Cargo.toml</code></p>
<pre><code class="language-toml">[dependencies]
lambda_runtime = &quot;0.3.0&quot;
serde = &quot;1.0.57&quot;
serde_json = &quot;1.0.57&quot;
serde_derive = &quot;1.0.57&quot;
tokio = { version=&quot;1.4.0&quot;, features=[&quot;rt-multi-thread&quot;, &quot;macros&quot;] }
warpgrapher = { version=&quot;0.10.0&quot;, features = [&quot;gremlin&quot;] }
</code></pre>
<p>In the <code>main.rs</code> source file, include the following code to include structs and functions that are needed from dependencies.</p>
<pre><code>use api_service::{create_app_engine, Error};
use lambda_runtime::handler_fn;
use serde_derive::Deserialize;
use serde_json::{json, Value};
use std::collections::HashMap;
use std::env::var_os;
use std::sync::Arc;
use warpgrapher::engine::database::gremlin::GremlinEndpoint;
use warpgrapher::engine::database::DatabaseEndpoint;
use warpgrapher::juniper::BoxFuture;
</code></pre>
<p>Next the lambda integration defines a <code>GraphqlRequest</code> struct that is used to deserialize query strings and request variables from the lambda interface for passing to the Warpgrapher engine.</p>
<pre><code>#[derive(Clone, Debug, Deserialize)]
pub struct GraphqlRequest {
    pub query: String,
    pub variables: Option&lt;Value&gt;,
}
</code></pre>
<p>The <code>AwsLambdaProxyRequest</code> struct is used to deserialize requests incoming from AWS lambda. Within the body of the request is the content that will be deserialized into the <code>GraphqlRequest</code> struct described above.</p>
<pre><code>#[derive(Clone, Debug, Deserialize)]
pub struct AwsLambdaProxyRequest {
    pub body: String,
    #[serde(rename = &quot;requestContext&quot;)]
    pub request_context: AwsLambdaProxyRequestContext,
}
</code></pre>
<p>The <code>aws_proxy_response</code> function below packages a result returned by a Warpgrapher engine's <code>execute</code> function into a format that can be returned to the AWS lambda framework.</p>
<pre><code>pub fn aws_proxy_response(body: serde_json::Value) -&gt; Result&lt;JSON, Error&gt; {
    Ok(json!({
      &quot;body&quot;: serde_json::to_string(&amp;body)
        .map_err(|e| Error::JsonSerializationError { source: e})?,
      &quot;headers&quot;: json!({}),
      &quot;isBase64Encoded&quot;: false,
      &quot;statusCode&quot;: 200
    }))
}
</code></pre>
<p>The <code>create_app_engine</code> function takes a database pool, Gremlin in this example, and returns a Warpgrapher <code>Engine</code> that can be used to handle GraphQL queries.</p>
<pre><code>    static CONFIG: &amp;str = &quot; version: 1
      model:
        - name: User
          props:
            - name: email
              type: String
    &quot;;

    // create config
    let config = Configuration::try_from(CONFIG.to_string()).expect(&quot;Failed to parse CONFIG&quot;);

    // create warpgrapher engine
    let engine: Engine&lt;Rctx&gt; = Engine::&lt;Rctx&gt;::new(config, db).build()?;

    Ok(engine)
}
</code></pre>
<p>The <code>main</code> function ties the above elements together to process a GraphQL query when the lambda function is invoked. The function creates a database pool from environment variables, as described in the <a href="integrations/./configuration/databases.html">Databases</a> section of the book. The <code>main</code> function then uses the <code>create_app_engine</code> function to create a Warpgrapher <code>Engine</code>. A closure is defined that deserializes the request from the AWS lambda function and passes it to the Warpgrapher engine for execution using the <code>execute</code> method.  The results are packaged up for response using the <code>aws_proxy_response</code> method.  That handler closure is passed to the lambda runtime for invocation when requests need to be processed.</p>
<pre><code>#[tokio::main]
async fn main() -&gt; Result&lt;(), Error&gt; {
    // define database endpoint
    let endpoint = GremlinEndpoint::from_env()?;
    let db = endpoint.pool().await?;

    // create warpgrapher engine
    let engine = Arc::new(create_app_engine(db).await?);

    let func = handler_fn(
        move |event: Value, _: lambda_runtime::Context| -&gt; BoxFuture&lt;Result&lt;Value, Error&gt;&gt; {
            let eng = engine.clone();

            Box::pin(async move {
                let engine = eng.clone();

                // parse handler event as aws proxy request and extract graphql request
                let proxy_request = serde_json::from_value(event).map_err(|e| 
                    Error::JsonDeserializationError {
                        desc: &quot;Failed to deserialize aws proxy request&quot;.to_string(),
                        source: e,
                    })?;
                let gql_request = serde_json::from_str(&amp;proxy_request.body).map_err(|e| 
                    Error::JsonDeserializationError {
                        desc: &quot;Failed to deserialize graphql request in body&quot;.to_string(),
                        source: e,
                    })?;

                // execute request
                let result = engine
                    .execute(
                        gql_request.query.to_string(),
                        gql_request.variables,
                        HashMap::new(),
                    )
                    .await?;

                // format response for api-gateway proxy
                aws_proxy_response(result)
                    .or_else(|e| aws_proxy_response(json!({ &quot;errors&quot;: [format!(&quot;{}&quot;, e)] })))
            })
        },
    );

    lambda_runtime::run(func)
        .await
        .map_err(|_| Error::LambdaError {})?;
    Ok(())
}
</code></pre>
<div style="break-before: page; page-break-before: always;"></div><h1 id="introduction"><a class="header" href="#introduction">Introduction</a></h1>
<p>Warpgrapher is published as a Rust crate. There are crate features for each of the databases supported as a back-end. For Gremlin-based databases such as Apache Tinkerpop and Azure CosmosDB, use the <code>gremlin</code> feature.</p>
<pre><code class="language-toml">[dependencies]
warpgrapher = { version = &quot;0.10.0&quot;, features = [&quot;gremlin&quot;] }
</code></pre>
<p>For Cypher-based databases, such as AWS Neptune and Neo4j, use the cypher feature.</p>
<pre><code class="language-toml">[dependencies]
warpgrapher = { version = &quot;0.10.0&quot;, features = [&quot;cypher&quot;] }
</code></pre>
<p>The database features are not mutually exclusive, so building with both features enabled will not do any harm. However, only one database may be used for an instance of the Warpgrapher engine. Compiling with no database features selected will succeed, but the resulting engine will have sharply limited functionality, as it will have no ability to connect to a back-end storage mechanism.</p>
<p>Continue for a tutorial on using Warpgrapher to build a web service.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="databases"><a class="header" href="#databases">Databases</a></h1>
<p>Warpgrapher translates GraphQL queries into CRUD operations against a back-end data store, based on a configuration specifying a data model. The tutorial will return to the topic of the <a href="configuration/./config.html">configuration file</a> soon, but the first step is configuring Warpgrapher to integrate with the back-end database. Without a graph database behind it, Warpgrapher's functionality is sharply limited.</p>
<p>Warppgrapher supports several database back-ends for graph data:</p>
<ol>
<li>Apache Tinkerpop</li>
<li>AWS Neptune (Cypher variant)</li>
<li>Azure Cosmos DB (Gremlin variant)</li>
<li>Neo4J</li>
</ol>
<p>It may be possible to use Warpgrapher with other graph databases. The list above is the set that the maintainers have used previosuly. Using each of the databases above requires selecting the <a href="configuration/./intro.html">appropriate crate feature</a> and setting up environment variables to provide connection information to Warpgrapher, as described below.</p>
<p>Regardless of database, export an environment variable to control the size of the database 
connection pool:</p>
<pre><code class="language-bash">export WG_POOL_SIZE=8
</code></pre>
<p>If the <code>WG_POOL_SIZE</code> variable is not set, Warpgrapher defaults to a pool the same size as the 
number of CPUs detected. If the number of CPUs cannot be detected, Warpgrapher defaults to a pool
of 8 connections. </p>
<h2 id="gremlin-based-databases"><a class="header" href="#gremlin-based-databases">Gremlin-Based Databases</a></h2>
<p>For all gremlin-based databases, such as Apache Tinkerpop and Azure Cosmos DB the
following environment variables control connection to the database.</p>
<ul>
<li>WG_GREMLIN_HOST is the host name for the database to which to connect.</li>
<li>WG_GREMLIN_READ_REPICA provides a separate host name for read-only replica nodes, if being 
used for additional scalability. If not set, the read pool connects to the same host as the
read/write connection pool.</li>
<li>WG_GREMLIN_PORT provides the port to which Warpgrapher should connect.</li>
<li>WG_GREMLIN_USER is the username to use to authenticate to the database, if required.</li>
<li>WG_GREMLIN_PASS is the password to use to authenticate to the database, if required.</li>
<li>WG_GREMLIN_USE_TLS is set to <code>true</code> if Warpgrapher should connect to the database over a TLS 
connection, and <code>false</code> if not using TLS. Defaults to <code>true</code>.</li>
<li>WG_GREMLIN_VALIDATE_CERTS is set to <code>true</code> if Warpgrapher should validate the certificate used
for a TLS connection, and <code>false</code>. Defaults to <code>true</code>. Should only be set to false in non-production
environments.</li>
<li>WG_GREMLIN_LONG_IDS is set to <code>true</code> if Warpgrapher should use long integers for vertex and edge
identifiers. If <code>false</code>, Warpgrapher uses strings. Defaults to <code>false</code>. Consult your graph database's documentation to determine what values are valid for identifiers.</li>
<li>WG_GREMLIN_PARTITIONS is set to <code>true</code> if Warpgrapher should require a partition ID, and false if 
Warpgrapher should ignore or omit partition IDs. Defaults to <code>false</code>.</li>
<li>WG_GREMLIN_SESSIONS is set to <code>true</code> if Warpgrapher mutations should be conducted within a single
Gremlin session, which in some databases provides transactional semantics, and <code>false</code> if sessions 
should not be used. Defaults to <code>false</code>.</li>
<li>WG_GREMLIN_VERSION may be set to <code>1</code>, <code>2</code>, or <code>3</code>, to indicate the version of GraphSON 
serialization that should be used in communicating with the database. Defaults to <code>3</code>.</li>
</ul>
<p>Example configurations for supported databases are shown below. In many cases, some environment 
variables are omitted for each database where the defaults are correct.</p>
<h3 id="apache-tinkerpop"><a class="header" href="#apache-tinkerpop">Apache Tinkerpop</a></h3>
<p>Add Warpgrapher to your project config with the gremlin feature enabled.</p>
<p><code>cargo.toml</code></p>
<pre><code class="language-toml">[dependencies]
warpgrapher = { version = &quot;0.10.0&quot;, features = [&quot;gremlin&quot;] }
</code></pre>
<p>Set up environment variables to contact your Gremlin-based DB:</p>
<pre><code class="language-bash">export WG_GREMLIN_HOST=localhost
export WG_GREMLIN_PORT=8182
export WG_GREMLIN_USER=username
export WG_GREMLIN_PASS=password
export WG_GREMLIN_USE_TLS=true
export WG_GREMLIN_VALIDATE_CERTS=true
export WG_GREMLIN_LONG_IDS=true
</code></pre>
<p>The <code>WG_GREMLIN_CERT</code> environment variable is true if Warpgrapher should ignore the validity of 
certificates. This may be necessary in a development or test environment, but should always be set
to false in production.</p>
<p>If you do not already have a Gremlin-based database running, you can run one using Docker:</p>
<pre><code class="language-bash">docker run -it --rm -p 8182:8182 tinkerpop/gremlin-server:latest
</code></pre>
<p>To use an interactive gremlin console to manually inspect test instances, run</p>
<pre><code class="language-bash">docker build -t gremlin-console -f tests/fixtures/gremlin-console/Dockerfile tests/fixtures/gremlin-console
docker run -i --net=host --rm gremlin-console:latest
</code></pre>
<p>In the console, connect to the remote graph:</p>
<pre><code>:remote connect tinkerpop.server conf/remote.yaml
:remote console
</code></pre>
<h3 id="aws-neptune"><a class="header" href="#aws-neptune">AWS Neptune</a></h3>
<p>Add Warpgrapher to your project config:</p>
<p><code>cargo.toml</code></p>
<pre><code class="language-toml">[dependencies]
warpgrapher = { version = &quot;0.10.0&quot;, features = [&quot;cypher&quot;] }
</code></pre>
<p>Then set up environment variables to contact your Neptune DB:</p>
<pre><code class="language-bash">export WG_CYPHER_HOST=127.0.0.1
export WG_CYPHER_READ_REPLICAS=127.0.0.1
export WG_CYPHER_PORT=7687
export WG_CYPHER_USER=
export WG_CYPHER_PASS=
</code></pre>
<h3 id="azure-cosmos-db"><a class="header" href="#azure-cosmos-db">Azure Cosmos DB</a></h3>
<p>Add Warpgrapher to your project config:</p>
<p><code>cargo.toml</code></p>
<pre><code class="language-toml">[dependencies]
warpgrapher = { version = &quot;0.10.0&quot;, features = [&quot;gremlin&quot;] }
</code></pre>
<p>Then set up environment variables to contact your Cosmos DB:</p>
<pre><code class="language-bash">export WG_GREMLIN_HOST=*MY-COSMOS-DB*.gremlin.cosmos.azure.com
export WG_GREMLIN_PORT=443
export WG_GREMLIN_USER=/dbs/*MY-COSMOS-DB*/colls/*MY-COSMOS-COLLECTION*
export WG_GREMLIN_PASS=*MY-COSMOS-KEY*
export WG_GREMLIN_USE_TLS=true
export WG_GREMLIN_VALIDATE_CERTS=true
export WG_GREMLIN_PARTITIONS=true
export WG_GREMLIN_VERSION=1
</code></pre>
<p>Note that when setting up your Cosmos database, you must configure it to offer a Gremlin graph API.</p>
<p>Note also that you must set your partition key to be named <code>partitionKey</code>, as this name for the partition key is hard-coded into Warpgrapher.  (This could be changed. If that would be helpful to you, <a href="https://github.com/warpforge/warpgrapher/issues">file an issue</a> with a feature request to make the partition key name configurable.</p>
<p>Be advised that Gremlin traversals are not executed atomically within Cosmos DB. A traversal may 
fail part way through if, for example, one reaches the read unit capacity limit.  See 
<a href="https://medium.com/@jayanta.mondal/cosmos-db-graph-gremlin-api-how-to-executing-multiple-writes-as-a-unit-via-a-single-gremlin-2ce82d8bf365">this article</a> 
for details. The workaround proposed in the article helps, but even idempotent queries do not 
guarantee atomicity.  Warpgrapher does not use idempotent queries with automated retries to overcome
this shortcoming of Cosmos DB, so note that if using Cosmos, there is a risk that a failed query 
could leave partially applied results behind.</p>
<h2 id="neo4j"><a class="header" href="#neo4j">Neo4J</a></h2>
<p>Add Warpgrapher to your project config.</p>
<pre><code class="language-toml">[dependencies]
warpgrapher = { version = &quot;0.10.0&quot;, features = [&quot;cypher&quot;] }
</code></pre>
<p>Then set up environment variables to contact your Neo4J DB.</p>
<pre><code class="language-bash">export WG_CYPHER_HOST=127.0.0.1
export WG_CYPHER_READ_REPLICAS=127.0.0.1
export WG_CYPHER_PORT=7687
export WG_CYPHER_USER=neo4j
export WG_CYPHER_PASS=*MY-DB-PASSWORD*
</code></pre>
<p>Note that the <code>WG_CYPHER_READ_REPLICAS</code> variable is optional. It is used for Neo4J cluster 
configurations in which there are both read/write nodes and read-only replicas. If the 
<code>WG_CYPHER_READ_REPLICAS</code> variable is set, read-only queries will be directed to the read replicas,
whereas mutations will be sent to the instance(s) at <code>WG_CYPHER_HOST</code>.</p>
<p>If you do not already have a Neo4J database running, you can run one using Docker:</p>
<pre><code class="language-bash">docker run -e NEO4JAUTH=&quot;${WG_CYPHER_USER}:${WG_CYPHER_PASS}&quot; neo4j:4.4
</code></pre>
<div style="break-before: page; page-break-before: always;"></div><h1 id="warpgrapher-config"><a class="header" href="#warpgrapher-config">Warpgrapher Config</a></h1>
<p>The <a href="configuration/../warpgrapher/quickstart.html">Quickstart</a> demonstrated using a string constant to hold the Warpgrapher configuration. It is also possible to read the configuration from a YAML file or to build a configuration programmatically using the configuration module's API. The following three configurations are all equivalent.</p>
<h2 id="string-configuration"><a class="header" href="#string-configuration">String Configuration</a></h2>
<p>The following is the string constant from the <a href="configuration/../warpgrapher/quickstart.html">Quickstart</a>.</p>
<pre><code class="language-rust no_run noplayground">static CONFIG: &amp;str = &quot;
version: 1
model:
  - name: User
    props:
      - name: email
        type: String
        required: false
&quot;;
</code></pre>
<h2 id="yaml-file-configuration"><a class="header" href="#yaml-file-configuration">YAML File Configuration</a></h2>
<p>The same configuration can be created in a YAML file, as follows.</p>
<p><code>config.yaml</code></p>
<pre><code class="language-rust no_run noplayground">version: 1
model:

<span class="boring">  User
</span>  - name: User
    props:
      - name: email
        type: String
</code></pre>
<p>The configuration can then be loaded and used to set up a <code>Configuration</code> struct.</p>
<p><code>main.rs</code></p>
<pre><code class="language-rust no_run noplayground">    let config_file = File::open(&quot;config.yaml&quot;.to_string()).expect(&quot;Could not read file&quot;);
    let config = Configuration::try_from(config_file).expect(&quot;Failed to parse config file&quot;);
</code></pre>
<h2 id="programmatic-configuration"><a class="header" href="#programmatic-configuration">Programmatic Configuration</a></h2>
<p>The code below shows the creation of the same configuration programmatically.</p>
<pre><code class="language-rust no_run noplayground">    // build warpgrapher config
    let config = Configuration::new(
        1,
        vec![Type::new(
            &quot;User&quot;.to_string(),
            vec![Property::new(
                &quot;email&quot;.to_string(),
                UsesFilter::all(),
                &quot;String&quot;.to_string(),
                false,
                false,
                None,
                None,
            )],
            Vec::new(),
            EndpointsFilter::all(),
        )],
        vec![],
    );
</code></pre>
<p>The programmatic version includes some function arguments that do not appear in the YAML versions of the configuration, because they take on default values when omitted from a YAML configuration.  For example, the <code>UsesFilter</code> on a property allows granular control over whether a property is included in create, read, update, and delete operations.  This allows, among other things, the creation of read-only attributes.  Similarly, the <code>EndpointsFilter</code> determines whether the <code>User</code> type has create, read, update, and delete operations exposed in the GraphQL schema. For example, if users are created by a separate account provisioning system, it might be desirable to filter out the create operation, so that the GraphQL schema doesn't allow the possibility of creating new users.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="types"><a class="header" href="#types">Types</a></h1>
<p>The <a href="configuration/../warpgrapher/quickstart.html">Quickstart</a> presented a first example of a Warpgrapher configuration, shown again here.</p>
<pre><code class="language-rust no_run noplayground">static CONFIG: &amp;str = &quot;
version: 1
model:
  - name: User
    props:
      - name: email
        type: String
        required: false
&quot;;
</code></pre>
<h2 id="type-configuration"><a class="header" href="#type-configuration">Type Configuration</a></h2>
<p>Recall that the <code>version</code> value is used to indicate the configuration file format version to be used. Right now, the only valid value is 1.  The next element in the configuration a a data model.  The <code>model</code> object is a list of types. The example shown in the <a href="configuration/../warpgrapher/quickstart.html">Quickstart</a> uses many defaults for simplicity. The definition below shows the full range of options for property definitions. Don't worry about relationships between types for the moment. Those are covered in the <a href="configuration/./relationships.html">next section</a>.</p>
<pre><code class="language-yaml">model:
  - name: String
    props:
      - name: String
        uses:
          create: Boolean
          query: Boolean
          update: Boolean
          output: Boolean
        type: String  # Boolean | Float | ID | Int | String
        required: Boolean
        list: Boolean
        resolver: String
        validator: String
    endpoints:
      read: Boolean
      create: Boolean
      update: Boolean
      delete: Boolean
</code></pre>
<p>Right under the model object is a list of types. The first attribute describing a type is a name. In the example from the <a href="configuration/../warpgrapher/quickstart.html">Quickstart</a>, the name of the type is <code>User</code>.</p>
<p>The second attribute describing a type is <code>props</code>. The <code>props</code> attribute is a list of properties that are stored on nodes of that type. Each property is described the several configuration attributes, as follows.</p>
<p>The <code>name</code> attribute is a string that identifies the property. It must be unique within the scope of the type. In the <a href="configuration/../warpgrapher/quickstart.html">Quickstart</a> example, the sole property on the User type is named email.</p>
<p>The <code>uses</code> attribute is an object that contains four fields within it, <code>create</code>, <code>query</code>, <code>update</code>, and <code>output</code>, each a boolean value. The fields within the <code>uses</code> attribute control whether the property is present in various parts of the GraphQL schema. If the <code>create</code> attribute is true, then the property will be included in the GraphQL input for creation operations. If false, the property will be omitted from creation operations. If the <code>query</code> attribute is true, the property will be included in the GraphQL schema for search query input. If false, the property will be omitted from search query operations. If the <code>update</code> attribute is true, the property will be included in the GraphQL schema input for updating existing nodes. If false, the property will be omitted from the update schema. Lastly, if the <code>output</code> attribute is true, the property will be included in the GraphQL schema for nodes returned to the client. If false, the property will be omitted from the output.</p>
<p>By default, all <code>uses</code> boolean attributes are true, meaning that the property is included in all relevant areas of the GraphQL schema. Selectively setting some of the <code>uses</code> attributes handles uses cases where a property should not be available for some operations. For example, one might set the <code>create</code> attribute to false if a property is a calculated value that should never be set directly.  One might set <code>update</code> to false to make an attribute immutable -- for example, the <code>email</code> property of the <code>User</code> type might have <code>update</code> set to false if GraphQL clients should not be able to tamper with the identities of users.  One might set <code>output</code> to false for properties that should never be read through the GraphQL interface, such as for keeping people from reading out a password property.</p>
<p>The <code>type</code> attribute of the property definition is a String value that must take on a value of <code>Boolean</code>, <code>Float</code>, <code>ID</code>, <code>Int</code>, or <code>String</code>, defining type of the property.</p>
<p>If the <code>required</code> attribute of the property definition is false, the property is not required (it is optional). By default this attribute is true, which means it must be provided when nodes of this type are created (unless hidden from the <code>create</code> use) and it must be present (non-null) when retrieving the node from Warpgrapher (again, unless hidden from the <code>output</code> use).</p>
<p>If the <code>list</code> attribute of the property definition is true, the property is a list of scalar values of <code>type</code>. If <code>list</code> is false, the property is only a single value of that scalar type.</p>
<p>The <code>resolver</code> attribute is a text key that is used to identify a custom-written resolver function. Warpgrapher allows applications to define custom resolvers that do more or different things than the default CRUD operations automatically provided by Warpgrapher itself.  For example, a custom resolver might dynamically calculate a value, such as a total or average, rather than just returning a value from the database.  Custom resolvers for <a href="configuration/../engine/dynamic_props.html">dynamic properties</a> are covered in greater detail later in the book.</p>
<p>The <code>validator</code> attribute is a text key that is used to identify a fuction that validates an input. For example, a validation function might check an email against and email validation regex. <a href="configuration/../engine/validators.html">Validation functions</a> are covered in greater detail later in the book.</p>
<p>Note that the <code>endpoints</code> attribute is on the <code>type</code> definition, not the <code>property</code> definition, as indicated by the indentation in the YAML example above. The <code>endpoints</code> attribute is somewhat similar to the <code>uses</code> boolean, but at the level of the whole type rather than a single property. If the <code>read</code> attribute is true, Warpgrapher will generate a query in the GraphQL schema so that node of this type can be retrieved. If false, no query will be generated. If the <code>create</code> attribute is true, Warpgrapher will generate a node creation mutation in the GraphQL schema. If false, no creation mutation will be generated. If the <code>update</code> attribute is true, Warpgrapher will generate a node update mutation in the GraphQL schema. If false, no update mutation will be generated. Lastly, if the <code>delete</code> attribute is true, Warpgrapher will generate a node deletion mutation in the GraphQL schema. If false, no delete mutation will be generated.</p>
<h2 id="generated-schema"><a class="header" href="#generated-schema">Generated Schema</a></h2>
<p>Warpgrapher uses the configuration described above to automatically generate a GraphQL schema and default resolver to create, read, update, and delete nodes of the types defined in the configuration's model section.  The remainder of this section walks through the contents of the schema in detail.</p>
<p>The top level GraphQL Query has two queries within it, as shown below. The <code>_version</code> query returns a scalar <code>String</code> with the version of the GraphQL service. The value returned is set with the <a href="https://docs.rs/warpgrapher/latest/warpgrapher/engine/struct.EngineBuilder.html#method.with_version">with_version</a> method on the <code>EngineBuilder</code>.</p>
<pre><code>type Query {
  User(input: UserQueryInput, partitionKey: String): [User!]
  _version: String
}
</code></pre>
<p>The <code>User</code> query, above, is generated by Warpgrapher for the retrieval of User nodes. The query takes two parameters, an <code>input</code> parameter that provides any search parameters that narrow down the set of Users to be retrieved, and a <code>partitionKey</code>.  The <code>partitionKey</code> is described in more detail in the <a href="configuration/./databases.html">Databases</a> section of the book.  The query returns a <code>User</code> type.</p>
<p>The <code>UserQueryInput</code>, defined in the schema snippet below, is use to provide search parameters to identify the <code>User</code> nodes to return to the client. The <code>User</code> node configuration had only one property, <code>email</code>. Warpgrapher automatically adds an <code>id</code> property that contains a unique identifier for nodes. In the GraphQL schema, the id is always represented as a string. However, in some Gremlin back-ends, the id may be required to be an integer, in which case the id field in the GraphQL schema will be a String that can be successfully parsed into an integer value. </p>
<pre><code>input UserQueryInput {
  email: StringQueryInput
  id: StringQueryInput
}
</code></pre>
<p>Note that the types of both <code>email</code> and <code>id</code> are <code>StringQueryInput</code>, not a simple <code>String</code> scalar. This is because the query input allows for more than just an exact match.</p>
<pre><code>input StringQueryInput {
  CONTAINS: String
  EQ: String
  GT: String
  GTE: String
  IN: [String!]
  LT: String
  LTE: String
  NOTCONTAINS: String
  NOTEQ: String
  NOTIN: [String!]
}
</code></pre>
<p>The <code>StringQueryInput</code> has various options for matching a String more flexibly than an exact match. The <code>CONTAINS</code> operator looks for the associated String value anywhere in the target property (e.g. the <code>email</code> or <code>id</code> properties of a <code>User</code> node).  <code>EQ</code> looks for an exact match.  <code>GT</code> and <code>GTE</code> are greater-than and great-than-or-equals, which are useful for searching for ranges based on alphabetization, as do <code>LT</code> and <code>LTE</code>.  The <code>IN</code> operators allows for searching for any string that is within a given set of Strings.  <code>NOTCONTAINS</code> is the opposite of <code>CONTAINS</code>, looking for property values that do not contain the provided String.  <code>NOTEQ</code> looks for non-matching Strings. And finally, <code>NOTIN</code> matches property values that do not appear in the provided set of Strings.</p>
<pre><code>type User {
  email: String
  id: ID!
}
</code></pre>
<p>The <code>User</code> type is the definition of the output type for the <code>User</code> GraphQL query. The names are the same, but these are two distinct things in the GraphQL schema -- the <code>User</code> query returns an array of zero or more <code>User</code> types.  The <code>User</code> type is two fields, and <code>id</code> and an <code>email</code>.  The id is a unique identifier for that node, which may be an integer or a UUID, depending on the graph database used. The <code>email</code> string is the single property that was defined on the example schema.</p>
<pre><code>type Mutation {
  UserCreate(input: UserCreateMutationInput!, partitionKey: String): User
  UserDelete(input: UserDeleteInput!, partitionKey: String): Int
  UserUpdate(partitionKey: String, input: UserUpdateInput!): [User!]
}
</code></pre>
<p>In addition to providing queries to retrieve existing nodes, Warpgrapher also automatically generates GraphQL schema elements and resolvers for create, update, and delete operations. The schema snippet above shows the mutations that are generated for the <code>User</code> node in the example configuration.  All three of the mutations take a <code>partitionKey</code>, which was described in the section on queries, above. Additionally, all three mutations take an <code>input</code> value, that provides the information necessary to complete the create, update, or delete operation, respectively.  Creation operations return the created node. Update operations return all the nodes that were matched and updated.  Lastly, the delete operation returns the number of nodes that were deleted.  The input arguments are detailed below.</p>
<pre><code>input UserCreateMutationInput {
  email: String
  id: ID
}
</code></pre>
<p>The <code>UserCreateMutationInput</code> mutation input includes the email property defined in the example configuration. It also includes an <code>id</code> property. Note that the <code>id</code> property is optional. If not provided by the client, it will be set to a unique identifier by the Warpgrapher server. The reason that clients are permitted to set the <code>id</code> when creating nodes is to allow for offline mode support, which may require the creation of identifiers within local caches that should remain the same after synchronization with the server.</p>
<pre><code>input UserDeleteInput {
  DELETE: UserDeleteMutationInput
  MATCH: UserQueryInput
}

input UserDeleteMutationInput
</code></pre>
<p>The <code>UserDeleteInput</code> input is used to identify which nodes to delete. Note that the <code>MATCH</code> part of the argument is the very same <code>UserQueryInput</code> type used in the <code>User</code> query schema element above. So searching for which nodes to delete is the same input format used to search for nodes to return in a read query.  The <code>UserDeleteMutationInput</code> is empty right now, and may be omitted. It will become relevant later, in the discussion on relationships between nodes.</p>
<pre><code>input UserUpdateInput {
  MATCH: UserQueryInput
  SET: UserUpdateMutationInput
}

input UserUpdateMutationInput {
  email: String
}
</code></pre>
<p>Lastly, the <code>UserUpdateInput</code> input is provided to the udpate mutation in order to select the nodes that need to be updated and describe the update to be applied.  The <code>MATCH</code> attribute is used to identify what nodes require the update. Note that the type of the <code>MATCH</code> attribute is <code>UserQueryInput</code>, which is the same type used for searching for nodes in the GraphQL query above.  The <code>SET</code> attribute is used to provide the new values to which the matching nodes should be set. In this example, it is a single String value for the <code>email</code> of the <code>User</code>. Note that <code>id</code>s are set only at creation. They cannot be updated later.</p>
<h2 id="full-schema-listing"><a class="header" href="#full-schema-listing">Full Schema Listing</a></h2>
<p>The full schema, described in pieces above, is included below:</p>
<pre><code>input UserDeleteInput {
  DELETE: UserDeleteMutationInput
  MATCH: UserQueryInput
}

input UserQueryInput {
  email: StringQueryInput
  id: StringQueryInput
}

type Mutation {
  UserCreate(input: UserCreateMutationInput!, partitionKey: String): User
  UserDelete(input: UserDeleteInput!, partitionKey: String): Int
  UserUpdate(partitionKey: String, input: UserUpdateInput!): [User!]
}

input UserUpdateMutationInput {
  email: String
}

type Subscription

input UserUpdateInput {
  MATCH: UserQueryInput
  SET: UserUpdateMutationInput
}

type Query {
  User(input: UserQueryInput, partitionKey: String): [User!]
  _version: String
}

input UserDeleteMutationInput

type User {
  email: String
  id: ID!
}

input UserCreateMutationInput {
  email: String
  id: ID
}

input StringQueryInput {
  CONTAINS: String
  EQ: String
  GT: String
  GTE: String
  IN: [String!]
  LT: String
  LTE: String
  NOTCONTAINS: String
  NOTEQ: String
  NOTIN: [String!]
}
</code></pre>
<div style="break-before: page; page-break-before: always;"></div><h1 id="relationships"><a class="header" href="#relationships">Relationships</a></h1>
<p>The <a href="configuration/../warpgrapher/quickstart.html">Quickstart</a> example used a very simple model with only one type, containing one property. The <a href="configuration/./types.html">Types</a> section explored configuration file format and the resulting GraphQL schema in more detail. However, Warpgrapher can generate create, read, update, and delete operations for relationships between types as well. The configuration below includes describes two types and a relationship between them.</p>
<pre><code class="language-yaml">version: 1
model:
  - name: User
    props:
      - name: email
        type: String
        required: false
  - name: Organization
    props:
      - name: name
        type: String
        required: false
    rels:
      - name: members
        nodes: [User]
        list: true
        props:
          - name: joinDate
            type: String
            required: false
</code></pre>
<p>The configuration above adds a second type, called <code>Organization</code>. The definition of the organization type contains the <code>rels</code> attribute, which was not seen in the earlier example. The <code>rels</code> attribute contains a list of permissible relationships between nodes. In this case, the configuration adds a <code>members</code> relationship from nodes of of the <code>Organization</code> type to nodes of the <code>User</code> type, indicating that certain users are members of an organization. The <code>name</code> attribute in the configuration is the name of the relationship and must be unique within the scope of that type. The <code>nodes</code> attribute is a list of other types that may be at the destination end of the relationship. In this case, the only type at may be a member is the <code>User</code> type, but in other use cases, the destination node might be allowed to be one of several types.  Lastly, the <code>list</code> attribute is <code>true</code>, indicating that an <code>Organization</code> may have more than one member.</p>
<h2 id="relationship-configuration"><a class="header" href="#relationship-configuration">Relationship Configuration</a></h2>
<p>The example configuration above is fairly simple, and does not make use of several optional attributes. The definition below shows the full set of configuration options that are permissible on a relationship.</p>
<pre><code class="language-yaml">model:
  - name: String
    rels:
      - name: String
        nodes: [String]  # Values in the list must be other types in the model
        list: Boolean
        props:
          - name: String
            uses:
              create: Boolean
              query: Boolean
              update: Boolean
              output: Boolean
            type: String  # Boolean | Float | ID | Int | String
            required: Boolean
            list: Boolean
            resolver: String
            validator: String
        endpoints:
          read: Boolean
          create: Boolean
          update: Boolean
          delete: Boolean
        resolver: String
</code></pre>
<p>The snippet above shows that relationships are defined in a list under the <code>rels</code> attribute within a type definition. Each relationship has a <code>name</code> that must be unique within the scope of that type. The <code>nodes</code> attribute is a list of name of types within the model that can appear as destination nodes in the relationship. Note that the a type may appear in its own relationship's <code>nodes</code> lists. A node is permitted to have relationships to nodes of the same type.</p>
<p>If the <code>list</code> attribute is <code>true</code>, then a node may have relationships of the same type to multiple destination nodes, modeling one-to-many relationships. If <code>list</code> is false, then the node may only have a single relationship of that type, to a single destination node.</p>
<p>The <code>props</code> attribute on a relationship works the same way that the <code>props</code> attribute works on nodes, except that the properties are associated with the relationship rather than with the node.  See the description of the <code>props</code> attribute in the section on <a href="configuration/./types.html">types</a> for more details.</p>
<p>Similarly, the <code>endpoints</code> attribute on relationships works the same way that it does on nodes. The individual boolean attributes within the <code>endpoints</code> object control whether Warpgrapher generates GraphQL schema elements for create, read, update, and delete operations. Just as with types, the default for all the boolean values is <code>true</code>, meaning that by default Warpgrapher creates schema elements and resolvers for all CRUD operations.</p>
<p>Lastly, the <code>resolver</code> attribute is also similar to the attribute of the same name on property definitions. The string in the <code>resolver</code> attribute is mapped to a custom-written Rust function provided when setting up the Warpgrapher engine. This allows systems using Warpgrapher to control the behavior of resolving some relationships. Use cases for this include dynamically-generated relationships that are computed at query time rather than being stored in the back-end data store.</p>
<h2 id="generated-schema-1"><a class="header" href="#generated-schema-1">Generated Schema</a></h2>
<p>This section describes each of the GraphQL schema elements that Warpgrapher generates for CRUD operations on relationships. Discussion of the schema elements related solely to types, absent relationships, was covered previously in the <a href="configuration/./types.html">types section</a>.</p>
<h3 id="queries-in-a-model-with-relationships"><a class="header" href="#queries-in-a-model-with-relationships">Queries in a Model with Relationships</a></h3>
<p>The top level GraphQL query object includes three (3) queries. This should make intuitive sense. The model has two nodes, <code>Organization</code> and <code>User</code>, and one relationship, the <code>OrganizationMembers</code> relationship from a source organization to a destination user that is a member of that organization. Warpgrapher's generated schema allows for querying either node type or the relationship between them. As will be discussed in detail below, the inputs to these query operations have a recursive structure, so that using the top level query for the relationship, it is possible to filter based on the properties of the source or destination nodes. Similarly, when querying for a node type, it is possible to add search parameters related to relationships, the destinations of those relationships, and so on.</p>
<pre><code>type Query {
  Organization(
    partitionKey: String
    input: OrganizationQueryInput
  ): [Organization!]
  OrganizationMembers(
    partitionKey: String
    input: OrganizationMembersQueryInput
  ): [OrganizationMembersRel!]
  User(partitionKey: String, input: UserQueryInput): [User!]
  _version: String
}
</code></pre>
<h3 id="querying-for-a-relationship"><a class="header" href="#querying-for-a-relationship">Querying for a Relationship</a></h3>
<p>In the top level GraphQL query, note that a new query, called <code>OrganizationMembers</code> has been generated for the relationship. This query has an input parameter, <code>OrganizationMembersQueryInput</code> that provides search query arguments to select the set of relationships to be returned.</p>
<p>The <code>OrganizationMembersQueryInput</code> query parameter, defined below, provides a means to search for a given instance of a relationship.  It is possible to search based on an <code>id</code> or set of IDs, and the <code>joinDate</code> attribute allows queries based on the properties on the relationship. In addition to using the <code>id</code> or another property on the relationship, the <code>OrganizationMembersQueryInput</code> parameter also includes a <code>src</code> and a <code>dst</code> attribute. These attributes allow Warpgrapher clients to search for relationships based on properties of the source or destination nodes joined by the relationship.</p>
<pre><code>input OrganizationMembersQueryInput {
  dst: OrganizationMembersDstQueryInput
  id: StringQueryInput
  joinDate: StringQueryInput
  src: OrganizationMembersSrcQueryInput
}
</code></pre>
<p>The two input objects for the <code>src</code> and <code>dst</code> input objects are shown below. Note that for the source query input, the only attribute is an <code>Organization</code> attribute that is an <code>OrganizationQueryInput</code> and that for the destination, the only attribute is a <code>User</code> attribute that is a <code>UserQueryInput</code>. There are two important observations here.  First, the reason for having the <code>OrganizationMembersDstQueryInput</code> object is that a relationship might have more than one node type as a possible destination. When building the GraphQL schema, Warpgrapher has to allow for the client to query any of those possible destination nodes. In this example, the only type of destination node is a <code>User</code>, so that's the only possibility shown below. If the nodes list had more types of nodes, any of those node types could be queried through the <code>OrganizationMembersDstQueryInput</code>.  The second observation is that both the <code>OrganizationQueryInput</code> and the <code>UserQueryInput</code> inputs are the same input parameters used to query for a set of nodes in the <code>Organization</code> and <code>User</code> root level GraphQL queries shown above.</p>
<pre><code>input OrganizationMembersSrcQueryInput {
  Organization: OrganizationQueryInput
}

input OrganizationMembersDstQueryInput {
  User: UserQueryInput
}
</code></pre>
<p>We'll come back to the node-based query input in a moment, in the section below on Querying for a Node. First, the code snippet below shows the schema for output from the relationship query. The relationship includes four attributes, a unique identifier for the relationship called <code>id</code>, <code>joinDate</code> for the property configured on the relationship, and <code>src</code> and <code>dst</code> attributes that represent the source and destination nodes respectively.</p>
<pre><code>type OrganizationMembersRel {
  dst: OrganizationMembersNodesUnion!
  id: ID!
  joinDate: String
  src: Organization!
}
</code></pre>
<p>The <code>src</code> attribute in the <code>OrganizationMembersRel</code> output type is an <code>Organization</code> type, which is exactly the same output type used for node queries, and so will be covered in the section on querying for nodes, below.  The <code>dst</code> attribute is a little more complex. Recall from the description of the configuration schema that Warpgrapher may connect from a source node type to a destination that can be one of many node types. A GraphQL union type is used to represent the multiple destination node types that may exist.  As shown in the schema snippet below, in this example of <code>OrganizationMembersNodesUnion</code>, there is only a single destination node type of User. A more complex configuration might have multiple node types in the union.</p>
<pre><code>union OrganizationMembersNodesUnion = User
</code></pre>
<p>Note that the <code>User</code> type is the same type that is used to return users in queries for nodes.</p>
<h3 id="querying-for-a-node"><a class="header" href="#querying-for-a-node">Querying for a Node</a></h3>
<p>The root GraphQL <code>Query</code> object has queries for each of the node types in the configuration.  To see how relationships affect node queries, have a look at the <code>Organization</code> query, beginning with the <code>OrganizationQueryInput</code> definition in the snippet below. In addition to the <code>id</code> and <code>name</code> attributes for searching based on the scalar properties of the type, the schema also includes a <code>members</code> attribute, of type <code>OrganizationMembersQueryInput</code>.  This is the same input object described above that's used in the root level query for the <code>OrganizationMembers</code> relationship. This recursive schema structure is really quite powerful, as it allows the client to query for nodes based on a combination of the node's property values, the values of properties in the relationships that it has, and the values of properties in the destination nodes at the other end of those relationships, to any level of depth.  For example, it would be easy to construct a query that retrieves all of the organizations that contain a particular user as a member. For examples of relationship-based queries, see the chapter on <a href="configuration/../api/intro.html">API usage</a>.</p>
<pre><code>input OrganizationQueryInput {
  id: StringQueryInput
  members: OrganizationMembersQueryInput
  name: StringQueryInput
}
</code></pre>
<p>Relationshps information can be navigated in the output type for the node, as well. The <code>Organization</code> output type shown in the snippet below includes both the scalar properties on the type, the <code>id</code> and <code>name</code>, as well as the relationship to the <code>members</code> of the Organization.  The <code>members</code> attribute includes an input of type <code>OrganizationMembersQueryInput</code>. This is the same input type that is used to query for members relationships from the GraphQL root query, desribed above. This means that when retrieving Organization nodes, it's possible to filter the set of members that you want to retrieve in a nested query. Again, the recursive structure of the schema generated by Warpgrapher allows you the flexibility to query to any level of depth in a sub-graph that is needed.</p>
<pre><code>type Organization {
  id: ID!
  members(input: OrganizationMembersQueryInput): [OrganizationMembersRel!]
  name: String
}
</code></pre>
<h3 id="mutations-in-a-model-with-relationships"><a class="header" href="#mutations-in-a-model-with-relationships">Mutations in a Model with Relationships</a></h3>
<p>The GraphQL schema's top level mutation object contains nine (9) mutations. This should make intuitive sense. There are three mutations (create, update, and delete), and three kinds of things that can be mutated: organization nodes, user nodes, and membership relationships between organizations and nodes. There are quite a few nested input and output types contributing to these mutations. The high-level principle to keep in mind is that Warpgrapher allows recursive operations that support manipulation of whole sub-graphs at a time. For example, node mutations have nested input objects that allow manipulation of the relationships on those nodes, and the destination nodes at the end of those relationships, and so on.</p>
<pre><code>type Mutation {
  OrganizationCreate(
    input: OrganizationCreateMutationInput!
    partitionKey: String
  ): Organization
  OrganizationDelete(partitionKey: String, input: OrganizationDeleteInput!): Int
  OrganizationMembersCreate(
    input: OrganizationMembersCreateInput!
    partitionKey: String
  ): [OrganizationMembersRel!]
  OrganizationMembersDelete(
    input: OrganizationMembersDeleteInput!
    partitionKey: String
  ): Int
  OrganizationMembersUpdate(
    partitionKey: String
    input: OrganizationMembersUpdateInput!
  ): [OrganizationMembersRel!]
  OrganizationUpdate(
    partitionKey: String
    input: OrganizationUpdateInput!
  ): [Organization!]
  UserCreate(input: UserCreateMutationInput!, partitionKey: String): User
  UserDelete(partitionKey: String, input: UserDeleteInput!): Int
  UserUpdate(partitionKey: String, input: UserUpdateInput!): [User!]
}
</code></pre>
<h3 id="mutating-a-relationship"><a class="header" href="#mutating-a-relationship">Mutating a Relationship</a></h3>
<h4 id="creating-a-relationship"><a class="header" href="#creating-a-relationship">Creating a Relationship</a></h4>
<p>The snippet below contains the input for creation of one or more OrganizationMembers relationships. There are two attributes, <code>MATCH</code> and <code>CREATE</code>. The <code>MATCH</code> attribute is used to identify the organization or organizations that should be matched as the source of the relationship(s) to be created. It has the same type, <code>OrganizationQueryInput</code> that is used to query for nodes using the <code>Organization</code> query under the GraphQL <code>Query</code> root described above.  The match query may select more than one node, allowing similar relationships to be created in bulk. Matching existing source nodes is the only option when creating a relationship. If it is necessary to create the node at the source end of the relationship, see the node creation operation, in this case <code>OrganizationCreate</code> instead.</p>
<pre><code>input OrganizationMembersCreateInput {
  CREATE: [OrganizationMembersCreateMutationInput!]
  MATCH: OrganizationQueryInput
}
</code></pre>
<p>The <code>CREATE</code> attribute has a type of <code>OrganizationMembersCreateMutationInput</code>. That input structure is shown in the schema snippet below. It includes the <code>joinDate</code> attribute on the relationship. The <code>id</code> object is accepted as an input to facilitate offline operation, in which the client may need to choose the unique identifier for the relationship. If the client does not choose the identifier, it will be randomly assigned by the Warpgrapher service.</p>
<pre><code>input OrganizationMembersCreateMutationInput {
  dst: OrganizationMembersNodesMutationInputUnion!
  id: ID
  joinDate: String
}
</code></pre>
<p>The <code>dst</code> property in the <code>OrganizationMembersCreateMutationInput</code> above is of type <code>OrganizationMembersNodesMutationInputUnion</code>, which is included in the schema snippet below. Don't be intimidated by the lengthy name of the union type. Recall that in the configuration above, the destination type of a relationship is allowed to have more than one type. In this configuration, it only has one type, but the <code>OrganizationMembersNodesMutationInputUnion</code> is what allows the destination of the relationship to have multiple types. In this case, the only option is <code>User</code>, with a type of <code>UserInput</code>.</p>
<pre><code>input OrganizationMembersNodesMutationInputUnion {
  User: UserInput
}
</code></pre>
<p>The <code>UserInput</code> type, which provides the destination node for the relationship(s) to be created, has two attributes. When using the <code>EXISTING</code> attribute, Warpgrapher search the graph database for a set of nodes matching the <code>UserQueryInput</code> search criteria and uses the results as the destination nodes for creation of the relationship(s). Note that this <code>UserQueryInput</code> type is the same input type that is used to query for users in the user query under the GraphQL root <code>Query</code>. No matter where in the recursive hierarchy, searhing for <code>User</code> nodes always uses the same input.  The <code>NEW</code> attribute creates a new <code>User</code> node as the destination of the relationship. Note that the <code>UserCreateMutationInput</code> input type is the same input type used to create a <code>User</code> node in the <code>UserCreate</code> mutation under the GraphQL root <code>Mutation</code> object.</p>
<pre><code>input UserInput {
  EXISTING: UserQueryInput
  NEW: UserCreateMutationInput
}
</code></pre>
<p>The output of creating one or more relationships, <code>OrganizationMembersRel</code>, is the same output type returned from querying for the organization's members relationship, as was described in the section on queries, above. It contains the newly created relationship.</p>
<h4 id="updating-a-relationship"><a class="header" href="#updating-a-relationship">Updating a Relationship</a></h4>
<p>The input for a relationship update mutation, <code>OrganizationMembersUpdateInput</code> is shown in the schema snippet below. The update input consists of two parts. The <code>MATCH</code> attribute is a query input to identify the relationships that should be updated. Note that the match input type, <code>OrganizationMembersQueryInput</code> is the same input type used to provide search parameters when searching for relationships under the <code>OrganizationMembers</code> query under the GraphQL root <code>Query</code> object.  The <code>SET</code> attribute is used to describe the changes that should be made to values in the relationship(s) matched by the <code>MATCH</code> parameter, and potentially the sub-graph beneath.</p>
<pre><code>input OrganizationMembersUpdateInput {
  MATCH: OrganizationMembersQueryInput
  SET: OrganizationMembersUpdateMutationInput!
}
</code></pre>
<p>The <code>SET</code> input is of type <code>OrganizationMembersUpdateMutationInput</code>, shown in the snippet below. The <code>joinDate</code> attribute is the same input type used during relationship creation operations, described in the section above. The <code>src</code> and <code>dst</code> attributes allow a single update to provide new values not only for the relationship properties, but also properties on the source and destination nodes at the ends of the relationship.</p>
<pre><code>input OrganizationMembersUpdateMutationInput {
  dst: OrganizationMembersDstUpdateMutationInput
  joinDate: String
  src: OrganizationMembersSrcUpdateMutationInput
}
</code></pre>
<p>The source and destination node input types are shown in the schema snippets below. Note that the types, <code>OrganizationUpdateMutationInput</code> and <code>UserUpdateMutationInput</code> are the same input types used for the <code>SET</code> attributes in the single node update operation, described in in the section on single-node mutation operations below. Thus, we have hit the point where the GraphQL schema structure that Warpgrapher generates is recursive. A relationship update mutation can update the properties on the relationship, as described just above, or using this recursive input structure, reach down into the source and destination nodes at the ends of the relationship and edit their properties as well.</p>
<pre><code>input OrganizationMembersSrcUpdateMutationInput {
  Organization: OrganizationUpdateMutationInput
}

input OrganizationMembersDstUpdateMutationInput {
  User: UserUpdateMutationInput
}
</code></pre>
<p>The output for updating one or more relationships, <code>OrganizationMembersRel</code>, is the same output type returned from querying for an organization's members relationship, as was described in the section on queries, above. For update operations, it returns the list of relationships that were updated in the mutation.</p>
<h4 id="deleting-a-relationship"><a class="header" href="#deleting-a-relationship">Deleting a Relationship</a></h4>
<p>The input for a relationship delete mutation, <code>OrganizationMembersDeleteInput</code>, is shown in the schema snippet below. The <code>MATCH</code> attribute is used to query for the relationships that are desired to be deleted. Note that the input type, <code>OrganizationMembersQueryInput</code> is the same input type used to query for relationships under the relationship query in the GraphQL root <code>Query</code> object, described in the section on querying, above.</p>
<pre><code>input OrganizationMembersDeleteInput {
  MATCH: OrganizationMembersQueryInput
  dst: OrganizationMembersDstDeleteMutationInput
  src: OrganizationMembersSrcDeleteMutationInput
}
</code></pre>
<p>The src and destination delete mutation inputs are not particularly interesting for this simple schema. The input type for the src of the relationship contains a single <code>Organization</code> attribute that has the same type as the deletion input for an <code>OrganizationDelete</code> mutation. However, the only option in that type is deletion of members, which is what is already being done. On the destination side, because the <code>User</code> type has no relationships of its own, the <code>UserDeleteMutationInput</code> object is empty altogether. Thus, for the most part, the <code>src</code> and <code>dst</code> attriubtes on the <code>OrganizationMembersDeleteInput</code> are not particularly useful, though in more complex models, they allows the possibility of deleting multiple nodes and relationships in a single query.</p>
<pre><code>input OrganizationMembersSrcDeleteMutationInput {
  Organization: OrganizationDeleteMutationInput
}

input OrganizationMembersDstDeleteMutationInput {
  User: UserDeleteMutationInput
}

input OrganizationDeleteMutationInput {
  members: [OrganizationMembersDeleteInput!]
}

input UserDeleteMutationInput
</code></pre>
<p>The output from the relationship deletion mutation is an integer with a count of the relationships deleted.</p>
<h3 id="mutating-a-node"><a class="header" href="#mutating-a-node">Mutating a Node</a></h3>
<p>In many ways, modifying a node in a data model that includes relationships is similar to what was described in the node-only portion of the book, previously. Thus, this section doesn't repeat that same content, instead focusing only on the changes the come from having a relationship in the mix.</p>
<h4 id="creating-a-node"><a class="header" href="#creating-a-node">Creating a Node</a></h4>
<p>The snippet below contains the input for creation of an organization. Note the <code>members</code> attribute, of type <code>OrganizationMembersCreateMutationInput</code>, which allows for the creation of members attributes in the same mutation that creates the organization.  The <code>OrganizationMembersCreateMutationInput</code> input type is the same one that is used for the <code>CREATE</code> attribute in the <code>OrganizationMembersCreate</code> mutation under the root GraphQL <code>mutation</code> object. Thus, when creating a node, you can create members for it using the same full flexbility provided by the mutation dedicated to creating relationships. The recursive nature of the creation inputs allows for the creation of entire sub-graphs.</p>
<pre><code>input OrganizationCreateMutationInput {
  id: ID
  members: [OrganizationMembersCreateMutationInput!]
  name: String
}
</code></pre>
<p>The rest of the inputs and output for the node creation mutation are the same as those described previously for a simpler model without relationships.</p>
<h3 id="updating-a-node"><a class="header" href="#updating-a-node">Updating a Node</a></h3>
<p>The <code>OrganizationUpdateInput</code> for making changes to organizations looks similar to the input types used for objects that don't have relationships. It has a <code>MATCH</code> attribute to select the objects to update, and a <code>SET</code> attribute to describe the changes to be made. The difference is in the </p>
<pre><code>input OrganizationUpdateInput {
  MATCH: OrganizationQueryInput
  SET: OrganizationUpdateMutationInput
}

input OrganizationUpdateMutationInput {
  members: [OrganizationMembersChangeInput!]
  name: String
}
</code></pre>
<p>The differences for the inclusion of relationships begin in the <code>OrganizationUpdateMutationInput</code> input type used to set new values for the nodes to be updated, which includes a <code>members</code> attribute of type <code>OrganizationMembersChangeInput</code>. There are three changes one could make to a relationship: add one or more new relationships to destination nodes, delete one or more relationships to destination nodes, or keep the relationships to the same set of destination nodes but make changes to the properties of one or more of those destination nodes.  Those options are captured in the <code>OrganizationMembersChangeInput</code> input type in the schema snippet below.</p>
<pre><code>input OrganizationMembersChangeInput {
  ADD: OrganizationMembersCreateMutationInput
  DELETE: OrganizationMembersDeleteInput
  UPDATE: OrganizationMembersUpdateInput
}
</code></pre>
<p>The <code>OrganizationMembersCreateMutationInput</code> input type for the <code>ADD</code> operation is the same one that was described above as the <code>CREATE</code> attribute the section on mutations to create new relationships. This makes sense, as in this context it is already clear what the source node or nodes are, and the <code>ADD</code> attribute need only create the new relationships to be added. Similarly, the <code>OrganizationMembersDeleteInput</code> used for the <code>DELETE</code> attribute here is the same one that is used for the <code>OrganizationMembersDelete</code> operation under the root GraphQL <code>Mutation</code> type. The match will be scoped to the relationships under the source node(s) selected by the <code>OrganizationUpdateInput</code> <code>MATCH</code> query. As expected, the same is true for the <code>OrganizationMembersUpdateInput</code> input type used for the <code>UPDATE</code> attribute. It's the same as the input used for the <code>OrganizationMembersUpdate</code> mutation under the root GraphQL <code>Mutation</code> type.</p>
<h3 id="deleting-a-node"><a class="header" href="#deleting-a-node">Deleting a Node</a></h3>
<p>The <code>OrganizationDeleteInput</code> input type, shown in the schema snippet below, looks similar to the one for nodes without relationships. However, the <code>OrganizationDeleteMutationInput</code> is different, as it includes a <code>members</code> attribute of type <code>OrganizationMembersDeleteInput</code>, which is the same type used for the <code>OrganizationMembersDelete</code> mutation under the GraphQL root <code>Mutation</code> type. In the case of this model, this additional input does little. In a more complex model with multiple types of relationships, however, it would allow for deletion of whole subgraphs of nodes and relationships.</p>
<pre><code>input OrganizationDeleteInput {
  DELETE: OrganizationDeleteMutationInput
  MATCH: OrganizationQueryInput
}

input OrganizationDeleteMutationInput {
  members: [OrganizationMembersDeleteInput!]
}
</code></pre>
<h2 id="full-schema-listing-1"><a class="header" href="#full-schema-listing-1">Full Schema Listing</a></h2>
<p>The full schema for the example above is included below.</p>
<pre><code>input OrganizationMembersDeleteInput {
  MATCH: OrganizationMembersQueryInput
  dst: OrganizationMembersDstDeleteMutationInput
  src: OrganizationMembersSrcDeleteMutationInput
}

input OrganizationCreateMutationInput {
  id: ID
  members: [OrganizationMembersCreateMutationInput!]
  name: String
}

input OrganizationMembersCreateInput {
  CREATE: [OrganizationMembersCreateMutationInput!]
  MATCH: OrganizationQueryInput
}

input OrganizationMembersSrcQueryInput {
  Organization: OrganizationQueryInput
}

type Mutation {
  OrganizationCreate(
    input: OrganizationCreateMutationInput!
    partitionKey: String
  ): Organization
  OrganizationDelete(partitionKey: String, input: OrganizationDeleteInput!): Int
  OrganizationMembersCreate(
    input: OrganizationMembersCreateInput!
    partitionKey: String
  ): [OrganizationMembersRel!]
  OrganizationMembersDelete(
    input: OrganizationMembersDeleteInput!
    partitionKey: String
  ): Int
  OrganizationMembersUpdate(
    partitionKey: String
    input: OrganizationMembersUpdateInput!
  ): [OrganizationMembersRel!]
  OrganizationUpdate(
    partitionKey: String
    input: OrganizationUpdateInput!
  ): [Organization!]
  UserCreate(input: UserCreateMutationInput!, partitionKey: String): User
  UserDelete(partitionKey: String, input: UserDeleteInput!): Int
  UserUpdate(partitionKey: String, input: UserUpdateInput!): [User!]
}

input OrganizationMembersChangeInput {
  ADD: OrganizationMembersCreateMutationInput
  DELETE: OrganizationMembersDeleteInput
  UPDATE: OrganizationMembersUpdateInput
}

input UserUpdateMutationInput {
  email: String
}

input UserDeleteInput {
  DELETE: UserDeleteMutationInput
  MATCH: UserQueryInput
}

input OrganizationMembersNodesMutationInputUnion {
  User: UserInput
}

input UserInput {
  EXISTING: UserQueryInput
  NEW: UserCreateMutationInput
}

input OrganizationQueryInput {
  id: StringQueryInput
  members: OrganizationMembersQueryInput
  name: StringQueryInput
}

union OrganizationMembersNodesUnion = User

type Query {
  Organization(
    partitionKey: String
    input: OrganizationQueryInput
  ): [Organization!]
  OrganizationMembers(
    partitionKey: String
    input: OrganizationMembersQueryInput
  ): [OrganizationMembersRel!]
  User(partitionKey: String, input: UserQueryInput): [User!]
  _version: String
}

input OrganizationMembersDstDeleteMutationInput {
  User: UserDeleteMutationInput
}

input OrganizationMembersUpdateInput {
  MATCH: OrganizationMembersQueryInput
  SET: OrganizationMembersUpdateMutationInput!
}

input OrganizationMembersSrcUpdateMutationInput {
  Organization: OrganizationUpdateMutationInput
}

input UserUpdateInput {
  MATCH: UserQueryInput
  SET: UserUpdateMutationInput
}

input OrganizationUpdateInput {
  MATCH: OrganizationQueryInput
  SET: OrganizationUpdateMutationInput
}

type OrganizationMembersRel {
  dst: OrganizationMembersNodesUnion!
  id: ID!
  joinDate: String
  src: Organization!
}

input OrganizationMembersUpdateMutationInput {
  dst: OrganizationMembersDstUpdateMutationInput
  joinDate: String
  src: OrganizationMembersSrcUpdateMutationInput
}

input OrganizationMembersSrcDeleteMutationInput {
  Organization: OrganizationDeleteMutationInput
}

input UserQueryInput {
  email: StringQueryInput
  id: StringQueryInput
}

input OrganizationMembersQueryInput {
  dst: OrganizationMembersDstQueryInput
  id: StringQueryInput
  joinDate: StringQueryInput
  src: OrganizationMembersSrcQueryInput
}

input OrganizationDeleteMutationInput {
  members: [OrganizationMembersDeleteInput!]
}

type Organization {
  id: ID!
  members(input: OrganizationMembersQueryInput): [OrganizationMembersRel!]
  name: String
}

input OrganizationUpdateMutationInput {
  members: [OrganizationMembersChangeInput!]
  name: String
}

type Subscription

input OrganizationMembersCreateMutationInput {
  dst: OrganizationMembersNodesMutationInputUnion!
  id: ID
  joinDate: String
}

input UserDeleteMutationInput

input OrganizationMembersDstUpdateMutationInput {
  User: UserUpdateMutationInput
}

type User {
  email: String
  id: ID!
}

input OrganizationMembersDstQueryInput {
  User: UserQueryInput
}

input UserCreateMutationInput {
  email: String
  id: ID
}

input StringQueryInput {
  CONTAINS: String
  EQ: String
  GT: String
  GTE: String
  IN: [String!]
  LT: String
  LTE: String
  NOTCONTAINS: String
  NOTEQ: String
  NOTIN: [String!]
}

input OrganizationDeleteInput {
  DELETE: OrganizationDeleteMutationInput
  MATCH: OrganizationQueryInput
}
</code></pre>
<div style="break-before: page; page-break-before: always;"></div><h1 id="warpgrapher-crud-api"><a class="header" href="#warpgrapher-crud-api">Warpgrapher CRUD API</a></h1>
<p>One of the primary features of Warpgrapher is the auto-generation of CRUD operations for all Types. This includes basic and advanced queries that support nested operations and graph traversals. The schema itself was described in the preceeding sections. This chapter provides a set of usage examples for the various queries and mutations, for both nodes and relationships.</p>
<p>For for more details on general GraphQL syntax, see: https://graphql.org/learn/.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="node-create"><a class="header" href="#node-create">Node Create</a></h1>
<p>The GraphQL API examples below use the example schema described in the <a href="api/../configuration/relationships.html">Relationships</a> section of the book. The unique IDs for nodes and relationships  in the examples below may differ than other sections and chapters of the book.</p>
<ul>
<li><a href="api/node_create.html#node-with-no-relationships">Node with No Relationships</a></li>
<li><a href="api/node_create.html#node-related-to-a-new-node">Node Related to a New Node</a></li>
<li><a href="api/node_create.html#node-related-to-an-existing-node">Node Related to an Existing Node</a></li>
</ul>
<h2 id="node-with-no-relationships"><a class="header" href="#node-with-no-relationships">Node with No Relationships</a></h2>
<p>The GraphQL query below creates a new organization.</p>
<pre><code>mutation {
  OrganizationCreate(input: { name: &quot;Warpforge&quot; }) {
    id
    name
  }
}
</code></pre>
<p>The output is as follows.</p>
<pre><code>{
  &quot;data&quot;: {
    &quot;OrganizationCreate&quot;: {
      &quot;id&quot;: &quot;edff7816-f40c-4be1-904a-b7ab62e60be1&quot;,
      &quot;name&quot;: &quot;Warpforge&quot;
    }
  }
}
</code></pre>
<h2 id="node-related-to-a-new-node"><a class="header" href="#node-related-to-a-new-node">Node Related to a New Node</a></h2>
<p>The GraphQL query below creates a new organization with a relationship to a member who is a new user.</p>
<pre><code>mutation {
  OrganizationCreate(
    input: {
      name: &quot;Just Us League&quot;
      members: {
        joinDate: &quot;2020-02-20&quot;,
        dst: { User: { NEW: { email: &quot;alistair@example.com&quot; } } }
      }
    }
  ) {
    id
    name
    members {
      id
      joinDate
      dst {
        ... on User {
          id
          email
        }
      }
    }
  }
}
</code></pre>
<p>The output is as follows.</p>
<pre><code>{
  &quot;data&quot;: {
    &quot;OrganizationCreate&quot;: {
      &quot;id&quot;: &quot;a33ab37b-af51-4ccd-88ee-7d4d6eb75de9&quot;,
      &quot;name&quot;: &quot;Just Us League&quot;,
      &quot;members&quot;: [
        {
          &quot;id&quot;: &quot;295d191f-0d66-484c-b1eb-39494f0ae8a0&quot;,
          &quot;joinDate&quot;: &quot;2020-02-20&quot;,
          &quot;dst&quot;: {
            &quot;id&quot;: &quot;5ca84494-dd14-468e-812f-cb2da07157db&quot;,
            &quot;email&quot;: &quot;alistair@example.com&quot;
          }
        }
      ]
    }
  }
}
</code></pre>
<h2 id="node-related-to-an-existing-node"><a class="header" href="#node-related-to-an-existing-node">Node Related to an Existing Node</a></h2>
<p>The GraphQL query below creates a new organization with new relationship to an existing member, alistair@example.com, the same user created in the example above.</p>
<pre><code>mutation {
  OrganizationCreate(
    input: {
      name: &quot;Consortia Unlimited&quot;
      members: {
        joinDate: &quot;2020-02-20&quot;,
        dst: { User: { EXISTING: { email: &quot;alistair@example.com&quot; } } }
      }
    }
  ) {
    id
    name
    members {
      id
      joinDate
      dst {
        ... on User {
          id
          email
        }
      }
    }
  }
}
</code></pre>
<p>The output is as follows:</p>
<pre><code>{
  &quot;data&quot;: {
    &quot;OrganizationCreate&quot;: {
      &quot;id&quot;: &quot;9ecef884-2afc-457e-8486-e1f84c761050&quot;,
      &quot;name&quot;: &quot;Consortia Unlimited&quot;,
      &quot;members&quot;: [
        {
          &quot;id&quot;: &quot;008fdc43-f3cf-48eb-a9e9-c5c753c65ee9&quot;,
          &quot;joinDate&quot;: &quot;2020-02-20&quot;,
          &quot;dst&quot;: {
            &quot;id&quot;: &quot;5ca84494-dd14-468e-812f-cb2da07157db&quot;,
            &quot;email&quot;: &quot;alistair@example.com&quot;
          }
        }
      ]
    }
  }
}
</code></pre>
<p>Note that the id for the member in this example is the same as that in the last example, because the relationship was created to the same user.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="node-read"><a class="header" href="#node-read">Node Read</a></h1>
<p>The GraphQL API examples below use the example schema described in the <a href="api/../configuration/relationships.html">Relationships</a> section of the book. The unique IDs for nodes and relationships  in the examples below may differ than other sections and chapters of the book.</p>
<ul>
<li><a href="api/node_read.html#all-nodes">All Nodes</a></li>
<li><a href="api/node_read.html#node-with-matching-properties">Node with Matching Properties</a></li>
<li><a href="api/node_read.html#node-with-matching-relationships">Node with Matching Relationships</a></li>
<li><a href="api/node_read.html#node-with-matching-destinations">Node with Matching Destinations</a></li>
</ul>
<h2 id="all-nodes"><a class="header" href="#all-nodes">All Nodes</a></h2>
<p>The GraphQL query below lists all organizations.</p>
<pre><code>query {
  Organization {
    id
    name
  }
}
</code></pre>
<p>The output is as follows.</p>
<pre><code>{
  &quot;data&quot;: {
    &quot;Organization&quot;: [
      {
        &quot;id&quot;: &quot;85faa40f-04a8-4f0a-ae44-804604b4ef4c&quot;,
        &quot;name&quot;: &quot;Just Us League&quot;
      },
      {
        &quot;id&quot;: &quot;5692bd2a-2bc9-4497-8285-1f7860478cd6&quot;,
        &quot;name&quot;: &quot;Consortia Unlimited&quot;
      },
      {
        &quot;id&quot;: &quot;1eea1d47-1fe8-4bed-9116-e0037fbdb296&quot;,
        &quot;name&quot;: &quot;Warpforge&quot;
      }
    ]
  }
}
</code></pre>
<h2 id="node-with-matching-properties"><a class="header" href="#node-with-matching-properties">Node with Matching Properties</a></h2>
<p>The GraphQL query below lists all organizations with the name <code>Warpforge</code>.</p>
<pre><code>query {
  Organization(input: { name: { EQ: &quot;Warpforge&quot; } }) {
    id
    name
  }
}
</code></pre>
<p>The output is as follows.</p>
<pre><code>{
  &quot;data&quot;: {
    &quot;Organization&quot;: [
      {
        &quot;id&quot;: &quot;1eea1d47-1fe8-4bed-9116-e0037fbdb296&quot;,
        &quot;name&quot;: &quot;Warpforge&quot;
      }
    ]
  }
}
</code></pre>
<h2 id="node-with-matching-relationships"><a class="header" href="#node-with-matching-relationships">Node with Matching Relationships</a></h2>
<p>The GraphQL query below lists all organizations with members that joined in 2020.</p>
<pre><code>query {
  Organization(
    input: { members: { joinDate: { CONTAINS: &quot;2020&quot; } } }
  ) {
    id
    name
    members {
      joinDate
      dst {
        ... on User {
          id
          email
        }
      }
    }
  }
}
</code></pre>
<p>The output is as follows:</p>
<pre><code>{
  &quot;data&quot;: {
    &quot;Organization&quot;: [
      {
        &quot;id&quot;: &quot;85faa40f-04a8-4f0a-ae44-804604b4ef4c&quot;,
        &quot;name&quot;: &quot;Just Us League&quot;,
        &quot;members&quot;: [
          {
            &quot;joinDate&quot;: &quot;2020-02-20&quot;,
            &quot;dst&quot;: {
              &quot;id&quot;: &quot;de5e58cd-eb5e-4bf8-8a7a-9656999f4013&quot;,
              &quot;email&quot;: &quot;alistair@example.com&quot;
            }
          }
        ]
      },
      {
        &quot;id&quot;: &quot;5692bd2a-2bc9-4497-8285-1f7860478cd6&quot;,
        &quot;name&quot;: &quot;Consortia Unlimited&quot;,
        &quot;members&quot;: [
          {
            &quot;joinDate&quot;: &quot;2020-02-20&quot;,
            &quot;dst&quot;: {
              &quot;id&quot;: &quot;de5e58cd-eb5e-4bf8-8a7a-9656999f4013&quot;,
              &quot;email&quot;: &quot;alistair@example.com&quot;
            }
          }
        ]
      }
    ]
  }
}
</code></pre>
<h2 id="node-with-matching-destinations"><a class="header" href="#node-with-matching-destinations">Node with Matching Destinations</a></h2>
<p>The GraphQL query below lists all the organizations of which the user <code>alistair@example.com</code> is a member.</p>
<pre><code>query {
  Organization(
    input: {
      members: { dst: { User: { email: { EQ: &quot;alistair@example.com&quot; } } } }
    }
  ) {
    id
    name
    members {
      joinDate
      dst {
        ... on User {
          id
          email
        }
      }
    }
  }
}
</code></pre>
<p>The output is as follows:</p>
<pre><code>{
  &quot;data&quot;: {
    &quot;Organization&quot;: [
      {
        &quot;id&quot;: &quot;85faa40f-04a8-4f0a-ae44-804604b4ef4c&quot;,
        &quot;name&quot;: &quot;Just Us League&quot;,
        &quot;members&quot;: [
          {
            &quot;joinDate&quot;: &quot;2020-02-20&quot;,
            &quot;dst&quot;: {
              &quot;id&quot;: &quot;de5e58cd-eb5e-4bf8-8a7a-9656999f4013&quot;,
              &quot;email&quot;: &quot;alistair@example.com&quot;
            }
          }
        ]
      },
      {
        &quot;id&quot;: &quot;5692bd2a-2bc9-4497-8285-1f7860478cd6&quot;,
        &quot;name&quot;: &quot;Consortia Unlimited&quot;,
        &quot;members&quot;: [
          {
            &quot;joinDate&quot;: &quot;2020-02-20&quot;,
            &quot;dst&quot;: {
              &quot;id&quot;: &quot;de5e58cd-eb5e-4bf8-8a7a-9656999f4013&quot;,
              &quot;email&quot;: &quot;alistair@example.com&quot;
            }
          }
        ]
      }
    ]
  }
}
</code></pre>
<div style="break-before: page; page-break-before: always;"></div><h1 id="node-update"><a class="header" href="#node-update">Node Update</a></h1>
<p>The GraphQL API examples below use the example schema described in the <a href="api/../configuration/relationships.html">Relationships</a> section of the book. The unique IDs for nodes and relationships  in the examples below may differ than other sections and chapters of the book.</p>
<ul>
<li><a href="api/node_update.html#match-node-properties">Match Node Properties</a></li>
<li><a href="api/node_update.html#match-destination-properties">Match Destination Properties</a></li>
<li><a href="api/node_update.html#add-a-destination-node">Add a Destination Node</a></li>
<li><a href="api/node_update.html#update-a-destination-node">Update a Destination Node</a></li>
<li><a href="api/node_update.html#delete-a-relationship">Delete a Relationship</a></li>
</ul>
<h2 id="match-node-properties"><a class="header" href="#match-node-properties">Match Node Properties</a></h2>
<p>The GraphQL query below match a node based on its properties and updates it.</p>
<pre><code>mutation {
  OrganizationUpdate(
    input: {
      MATCH: { name: { EQ: &quot;Warpforge&quot; } }
      SET: { name: &quot;Harsh Truth Heavy Industries&quot; }
    }
  ) {
    id
    name
  }
}

</code></pre>
<p>The output is as follows.</p>
<pre><code>{
  &quot;data&quot;: {
    &quot;OrganizationUpdate&quot;: [
      {
        &quot;id&quot;: &quot;1eea1d47-1fe8-4bed-9116-e0037fbdb296&quot;,
        &quot;name&quot;: &quot;Harsh Truth Heavy Industries&quot;
      }
    ]
  }
}
</code></pre>
<h2 id="match-destination-properties"><a class="header" href="#match-destination-properties">Match Destination Properties</a></h2>
<p>The GraphQL query below matches a node based on properties on a desination node to which it is related, then updates it.</p>
<pre><code>mutation {
  OrganizationUpdate(
    input: {
      MATCH: {
        members: { dst: { User: { email: { EQ: &quot;balthazar@example.com&quot; } } } }
      }
      SET: { name: &quot;Prophet and Loss Inc.&quot; }
    }
  ) {
    id
    name
    members {
      id
      joinDate
      dst {
        ... on User {
          id
          email
        }
      }
    }
  }
}
</code></pre>
<p>The output is as follows.</p>
<pre><code>{
  &quot;data&quot;: {
    &quot;OrganizationUpdate&quot;: [
      {
        &quot;id&quot;: &quot;5692bd2a-2bc9-4497-8285-1f7860478cd6&quot;,
        &quot;name&quot;: &quot;Prophet and Loss Inc.&quot;,
        &quot;members&quot;: [
          {
            &quot;id&quot;: &quot;78acc7ac-2153-413d-a8d7-688e472340d5&quot;,
            &quot;joinDate&quot;: &quot;2021-01-02&quot;,
            &quot;dst&quot;: {
              &quot;id&quot;: &quot;ea2a1b68-fda2-4adb-9c80-554761a1c97b&quot;,
              &quot;email&quot;: &quot;balthazar@example.com&quot;
            }
          },
          {
            &quot;id&quot;: &quot;00051bc1-133c-445d-b00c-4faf61b2bffa&quot;,
            &quot;joinDate&quot;: &quot;2020-02-20&quot;,
            &quot;dst&quot;: {
              &quot;id&quot;: &quot;de5e58cd-eb5e-4bf8-8a7a-9656999f4013&quot;,
              &quot;email&quot;: &quot;alistair@example.com&quot;
            }
          }
        ]
      }
    ]
  }
}
</code></pre>
<h2 id="add-a-destination-node"><a class="header" href="#add-a-destination-node">Add a Destination Node</a></h2>
<p>The GraphQL query below updates the Warpforge organization to add an additional, newly created user.  If an <code>EXISTING</code> attribute were used in place of <code>NEW</code> in the query below, one could query for existing users to add to the organization.</p>
<pre><code>mutation {
  OrganizationUpdate(
    input: {
      MATCH: { name: { EQ: &quot;Warpforge&quot; } }
      SET: {
        members: {
          ADD: {
            joinDate: &quot;2018-01-08&quot;,
            dst: { User: { NEW: { email: &quot;constantine@example.com&quot; } } }
          }
        }
      }
    }
  ) {
    id
    name
    members {
      id
      joinDate
      dst {
        ... on User {
          id
          email
        }
      }
    }
  }
}
</code></pre>
<p>The output is as follows.</p>
<pre><code>{
  &quot;data&quot;: {
    &quot;OrganizationUpdate&quot;: [
      {
        &quot;id&quot;: &quot;85faa40f-04a8-4f0a-ae44-804604b4ef4c&quot;,
        &quot;name&quot;: &quot;Warpforge&quot;,
        &quot;members&quot;: [
          {
            &quot;id&quot;: &quot;38cd72c8-75b5-4547-9829-38d6a6854eb9&quot;,
            &quot;joinDate&quot;: &quot;2018-01-08&quot;,
            &quot;dst&quot;: {
              &quot;id&quot;: &quot;f2e894bf-e98e-48a7-b16a-adc95cd34ac3&quot;,
              &quot;email&quot;: &quot;constantine@example.com&quot;
            }
          },
          {
            &quot;id&quot;: &quot;bd302b7f-8a3f-49ab-aac3-c3348d8b8d94&quot;,
            &quot;joinDate&quot;: &quot;2020-02-20&quot;,
            &quot;dst&quot;: {
              &quot;id&quot;: &quot;de5e58cd-eb5e-4bf8-8a7a-9656999f4013&quot;,
              &quot;email&quot;: &quot;alistair@example.com&quot;
            }
          }
        ]
      }
    ]
  }
}
</code></pre>
<h2 id="update-a-destination-node"><a class="header" href="#update-a-destination-node">Update a Destination Node</a></h2>
<p>The GraphQL query below updates a value on a destination node.</p>
<pre><code>mutation {
  OrganizationUpdate(
    input: {
      MATCH: { name: { EQ: &quot;Warpforge&quot; } }
      SET: {
        members: {
          UPDATE: {
            MATCH: {
              dst: { User: { email: { EQ: &quot;constantine@example.com&quot; } } }
            }
            SET: { dst: { User: { email: &quot;javier@example.com&quot; } } }
          }
        }
      }
    }
  ) {
    id
    name
    members {
      id
      joinDate
      dst {
        ... on User {
          id
          email
        }
      }
    }
  }
}
</code></pre>
<p>The output is as follows.</p>
<pre><code>{
  &quot;data&quot;: {
    &quot;OrganizationUpdate&quot;: [
      {
        &quot;id&quot;: &quot;85faa40f-04a8-4f0a-ae44-804604b4ef4c&quot;,
        &quot;name&quot;: &quot;Warpforge&quot;,
        &quot;members&quot;: [
          {
            &quot;id&quot;: &quot;38cd72c8-75b5-4547-9829-38d6a6854eb9&quot;,
            &quot;joinDate&quot;: &quot;2018-01-08&quot;,
            &quot;dst&quot;: {
              &quot;id&quot;: &quot;f2e894bf-e98e-48a7-b16a-adc95cd34ac3&quot;,
              &quot;email&quot;: &quot;javier@example.com&quot;
            }
          },
          {
            &quot;id&quot;: &quot;bd302b7f-8a3f-49ab-aac3-c3348d8b8d94&quot;,
            &quot;joinDate&quot;: &quot;2020-02-20&quot;,
            &quot;dst&quot;: {
              &quot;id&quot;: &quot;de5e58cd-eb5e-4bf8-8a7a-9656999f4013&quot;,
              &quot;email&quot;: &quot;alistair@example.com&quot;
            }
          }
        ]
      }
    ]
  }
}
</code></pre>
<h2 id="delete-a-relationship"><a class="header" href="#delete-a-relationship">Delete a Relationship</a></h2>
<p>The GraphQL query below deletes the relationship from the Warpforge organization to alistair@example.com, removing them as a member of the organization.</p>
<pre><code>mutation {
  OrganizationUpdate(
    input: {
      MATCH: { name: { EQ: &quot;Warpforge&quot; } }
      SET: {
        members: {
          DELETE: {
            MATCH: { dst: { User: { email: { EQ: &quot;alistair@example.com&quot; } } } }
          }
        }
      }
    }
  ) {
    id
    name
    members {
      id
      joinDate
      dst {
        ... on User {
          id
          email
        }
      }
    }
  }
}
</code></pre>
<p>The output is as follows.</p>
<pre><code>{
  &quot;data&quot;: {
    &quot;OrganizationUpdate&quot;: [
      {
        &quot;id&quot;: &quot;85faa40f-04a8-4f0a-ae44-804604b4ef4c&quot;,
        &quot;name&quot;: &quot;Warpforge&quot;,
        &quot;members&quot;: [
          {
            &quot;id&quot;: &quot;38cd72c8-75b5-4547-9829-38d6a6854eb9&quot;,
            &quot;joinDate&quot;: &quot;2018-01-08&quot;,
            &quot;dst&quot;: {
              &quot;id&quot;: &quot;f2e894bf-e98e-48a7-b16a-adc95cd34ac3&quot;,
              &quot;email&quot;: &quot;javier@example.com&quot;
            }
          }
        ]
      }
    ]
  }
}
</code></pre>
<div style="break-before: page; page-break-before: always;"></div><h1 id="node-delete"><a class="header" href="#node-delete">Node Delete</a></h1>
<p>The GraphQL API examples below use the example schema described in the <a href="api/../configuration/relationships.html">Relationships</a> section of the book. The unique IDs for nodes and relationships  in the examples below may differ than other sections and chapters of the book.</p>
<ul>
<li><a href="api/node_delete.html#node-with-matching-properties">Node with Matching Properties</a></li>
</ul>
<h2 id="node-with-matching-properties-1"><a class="header" href="#node-with-matching-properties-1">Node with Matching Properties</a></h2>
<p>The GraphQL query below deletes a node based on matching against its properties.</p>
<pre><code>mutation {
  OrganizationDelete(
    input: { MATCH: { name: { EQ: &quot;Harsh Truth Heavy Industries&quot; } } }
  )
}
</code></pre>
<p>The output is as follows, indicating that one organization was successfully deleted.</p>
<pre><code>{
  &quot;data&quot;: {
    &quot;OrganizationDelete&quot;: 1
  }
}
</code></pre>
<div style="break-before: page; page-break-before: always;"></div><h1 id="relationship-create"><a class="header" href="#relationship-create">Relationship Create</a></h1>
<p>The GraphQL API examples below use the example schema described in the <a href="api/../configuration/relationships.html">Relationships</a> section of the book. The unique IDs for nodes and relationships  in the examples below may differ than other sections and chapters of the book.</p>
<ul>
<li><a href="api/rel_create.html#between-existing-nodes">Between Existing Nodes</a></li>
<li><a href="api/rel_create.html#from-an-existing-to-a-new-node">From an Existing to a New Node</a></li>
</ul>
<h2 id="between-existing-nodes"><a class="header" href="#between-existing-nodes">Between Existing Nodes</a></h2>
<p>The GraphQL query below creates a new membership relationship between two existing nodes, adding alistair@example.com to the Warpforge projct.</p>
<pre><code>mutation {
  OrganizationMembersCreate(
    input: {
      MATCH: { name: { EQ: &quot;Warpforge&quot; } }
      CREATE: {
        joinDate: &quot;2022-01-28&quot;,
        dst: { User: { EXISTING: { email: { EQ: &quot;alistair@example.com&quot; } } } }
      }
    }
  ) {
    id
    joinDate
    src {
      id
      name
    }
    dst {
      ... on User {
        id
        email
      }
    }
  }
}
</code></pre>
<p>The output is as follows.</p>
<pre><code>{
  &quot;data&quot;: {
    &quot;OrganizationMembersCreate&quot;: [
      {
        &quot;id&quot;: &quot;21173765-b2a3-4bb1-bfa7-5787ef17d6a8&quot;,
        &quot;joinDate&quot;: &quot;2022-01-28&quot;,
        &quot;src&quot;: {
          &quot;id&quot;: &quot;85faa40f-04a8-4f0a-ae44-804604b4ef4c&quot;,
          &quot;name&quot;: &quot;Warpforge&quot;
        },
        &quot;dst&quot;: {
          &quot;id&quot;: &quot;de5e58cd-eb5e-4bf8-8a7a-9656999f4013&quot;,
          &quot;email&quot;: &quot;alistair@example.com&quot;
        }
      }
    ]
  }
}
</code></pre>
<h2 id="from-an-existing-to-a-new-node"><a class="header" href="#from-an-existing-to-a-new-node">From an Existing to a New Node</a></h2>
<p>The GraphQL below creates a new membership relationship from an existing organization to a newly created user.</p>
<pre><code>mutation {
  OrganizationMembersCreate(
    input: {
      MATCH: { name: { EQ: &quot;Warpforge&quot; } }
      CREATE: {
        joinDate: &quot;2022-01-28&quot;,
        dst: { User: { NEW: { email: &quot;constantine@example.com&quot; } } }
      }
    }
  ) {
    id
    joinDate
    src {
      id
      name
    }
    dst {
      ... on User {
        id
        email
      }
    }
  }
}
</code></pre>
<p>The output is as follows.</p>
<pre><code>{
  &quot;data&quot;: {
    &quot;OrganizationMembersCreate&quot;: [
      {
        &quot;id&quot;: &quot;3ab33be6-16a3-4e50-87b5-3bb7d195ea54&quot;,
        &quot;joinDate&quot;: &quot;2022-01-28&quot;,
        &quot;src&quot;: {
          &quot;id&quot;: &quot;85faa40f-04a8-4f0a-ae44-804604b4ef4c&quot;,
          &quot;name&quot;: &quot;Warpforge&quot;
        },
        &quot;dst&quot;: {
          &quot;id&quot;: &quot;c2b71308-2fd7-4d43-b037-30ec473e90a5&quot;,
          &quot;email&quot;: &quot;constantine@example.com&quot;
        }
      }
    ]
  }
}
</code></pre>
<div style="break-before: page; page-break-before: always;"></div><h1 id="relationship-read"><a class="header" href="#relationship-read">Relationship Read</a></h1>
<p>The GraphQL API examples below use the example schema described in the <a href="api/../configuration/relationships.html">Relationships</a> section of the book. The unique IDs for nodes and relationships  in the examples below may differ than other sections and chapters of the book.</p>
<ul>
<li><a href="api/rel_read.html#by-relationship-properties">By Relationship Properties</a></li>
<li><a href="api/rel_read.html#by-source-node">By Source Node</a></li>
<li><a href="api/rel_read.html#by-destination-node">By Destination Node</a></li>
</ul>
<h2 id="by-relationship-properties"><a class="header" href="#by-relationship-properties">By Relationship Properties</a></h2>
<p>The GraphQL query below retrieves all the members who joined organizations on 2018-01-08.</p>
<pre><code>query {
  OrganizationMembers(input: { joinDate: { EQ: &quot;2018-01-08&quot; } }) {
    id
    joinDate
    src {
      id
      name
    }
    dst {
      ... on User {
        id
        email
      }
    }
  }
}
</code></pre>
<p>The output is as follows.</p>
<pre><code>{
  &quot;data&quot;: {
    &quot;OrganizationMembers&quot;: [
      {
        &quot;id&quot;: &quot;38cd72c8-75b5-4547-9829-38d6a6854eb9&quot;,
        &quot;joinDate&quot;: &quot;2018-01-08&quot;,
        &quot;src&quot;: {
          &quot;id&quot;: &quot;85faa40f-04a8-4f0a-ae44-804604b4ef4c&quot;,
          &quot;name&quot;: &quot;Warpforge&quot;
        },
        &quot;dst&quot;: {
          &quot;id&quot;: &quot;f2e894bf-e98e-48a7-b16a-adc95cd34ac3&quot;,
          &quot;email&quot;: &quot;javier@example.com&quot;
        }
      }
    ]
  }
}
</code></pre>
<h2 id="by-source-node"><a class="header" href="#by-source-node">By Source Node</a></h2>
<p>The GraphQL query below retrieves all the members of users in the Warpforge organization.</p>
<pre><code>query {
  OrganizationMembers(
    input: { src: { Organization: { name: { EQ: &quot;Warpforge&quot; } } } }
  ) {
    id
    joinDate
    src {
      id
      name
    }
    dst {
      ... on User {
        id
        email
      }
    }
  }
}
</code></pre>
<p>The output is as follows.</p>
<pre><code>{
  &quot;data&quot;: {
    &quot;OrganizationMembers&quot;: [
      {
        &quot;id&quot;: &quot;3ab33be6-16a3-4e50-87b5-3bb7d195ea54&quot;,
        &quot;joinDate&quot;: &quot;2022-01-28&quot;,
        &quot;src&quot;: {
          &quot;id&quot;: &quot;85faa40f-04a8-4f0a-ae44-804604b4ef4c&quot;,
          &quot;name&quot;: &quot;Warpforge&quot;
        },
        &quot;dst&quot;: {
          &quot;id&quot;: &quot;c2b71308-2fd7-4d43-b037-30ec473e90a5&quot;,
          &quot;email&quot;: &quot;constantine@example.com&quot;
        }
      },
      {
        &quot;id&quot;: &quot;21173765-b2a3-4bb1-bfa7-5787ef17d6a8&quot;,
        &quot;joinDate&quot;: &quot;2022-01-28&quot;,
        &quot;src&quot;: {
          &quot;id&quot;: &quot;85faa40f-04a8-4f0a-ae44-804604b4ef4c&quot;,
          &quot;name&quot;: &quot;Warpforge&quot;
        },
        &quot;dst&quot;: {
          &quot;id&quot;: &quot;de5e58cd-eb5e-4bf8-8a7a-9656999f4013&quot;,
          &quot;email&quot;: &quot;alistair@example.com&quot;
        }
      },
      {
        &quot;id&quot;: &quot;38cd72c8-75b5-4547-9829-38d6a6854eb9&quot;,
        &quot;joinDate&quot;: &quot;2018-01-08&quot;,
        &quot;src&quot;: {
          &quot;id&quot;: &quot;85faa40f-04a8-4f0a-ae44-804604b4ef4c&quot;,
          &quot;name&quot;: &quot;Warpforge&quot;
        },
        &quot;dst&quot;: {
          &quot;id&quot;: &quot;f2e894bf-e98e-48a7-b16a-adc95cd34ac3&quot;,
          &quot;email&quot;: &quot;javier@example.com&quot;
        }
      }
    ]
  }
}
</code></pre>
<h2 id="by-destination-node"><a class="header" href="#by-destination-node">By Destination Node</a></h2>
<p>The GraphQL query below retrieves all of the members of alistair@example.com.</p>
<pre><code>query {
  OrganizationMembers(
    input: { dst: { User: { email: { EQ: &quot;alistair@example.com&quot; } } } }
  ) {
    id
    joinDate
    src {
      id
      name
    }
    dst {
      ... on User {
        id
        email
      }
    }
  }
}
</code></pre>
<p>The output is as follows.</p>
<pre><code>{
  &quot;data&quot;: {
    &quot;OrganizationMembers&quot;: [
      {
        &quot;id&quot;: &quot;21173765-b2a3-4bb1-bfa7-5787ef17d6a8&quot;,
        &quot;joinDate&quot;: &quot;2022-01-28&quot;,
        &quot;src&quot;: {
          &quot;id&quot;: &quot;85faa40f-04a8-4f0a-ae44-804604b4ef4c&quot;,
          &quot;name&quot;: &quot;Warpforge&quot;
        },
        &quot;dst&quot;: {
          &quot;id&quot;: &quot;de5e58cd-eb5e-4bf8-8a7a-9656999f4013&quot;,
          &quot;email&quot;: &quot;alistair@example.com&quot;
        }
      },
      {
        &quot;id&quot;: &quot;00051bc1-133c-445d-b00c-4faf61b2bffa&quot;,
        &quot;joinDate&quot;: &quot;2020-02-20&quot;,
        &quot;src&quot;: {
          &quot;id&quot;: &quot;5692bd2a-2bc9-4497-8285-1f7860478cd6&quot;,
          &quot;name&quot;: &quot;Prophet and Loss Inc.&quot;
        },
        &quot;dst&quot;: {
          &quot;id&quot;: &quot;de5e58cd-eb5e-4bf8-8a7a-9656999f4013&quot;,
          &quot;email&quot;: &quot;alistair@example.com&quot;
        }
      }
    ]
  }
}
</code></pre>
<div style="break-before: page; page-break-before: always;"></div><h1 id="relationship-update"><a class="header" href="#relationship-update">Relationship Update</a></h1>
<ul>
<li><a href="api/rel_update.html#update-relationship-properties">Update Relationship Properties</a></li>
</ul>
<h2 id="update-relationship-properties"><a class="header" href="#update-relationship-properties">Update Relationship Properties</a></h2>
<p>The GraphQL updates the date on a membership.</p>
<pre><code>mutation {
  OrganizationMembersUpdate(
    input: {
      MATCH: {
        src: { Organization: { name: &quot;Warpforge&quot; } }
        dst: { User: { email: &quot;alistair@example.com&quot; } }
      }
      SET: { joinDate: &quot;2021-12-31&quot; }
    }
  ) {
    id
    joinDate
    src {
      id
      name
    }
    dst {
      ... on User {
        id
        email
      }
    }
  }
}
</code></pre>
<p>The output is as follows.</p>
<pre><code>{
  &quot;data&quot;: {
    &quot;OrganizationMembersUpdate&quot;: [
      {
        &quot;id&quot;: &quot;21173765-b2a3-4bb1-bfa7-5787ef17d6a8&quot;,
        &quot;joinDate&quot;: &quot;2021-12-31&quot;,
        &quot;src&quot;: {
          &quot;id&quot;: &quot;85faa40f-04a8-4f0a-ae44-804604b4ef4c&quot;,
          &quot;name&quot;: &quot;Warpforge&quot;
        },
        &quot;dst&quot;: {
          &quot;id&quot;: &quot;de5e58cd-eb5e-4bf8-8a7a-9656999f4013&quot;,
          &quot;email&quot;: &quot;alistair@example.com&quot;
        }
      }
    ]
  }
}
</code></pre>
<div style="break-before: page; page-break-before: always;"></div><h1 id="relationship-delete"><a class="header" href="#relationship-delete">Relationship Delete</a></h1>
<ul>
<li><a href="api/rel_delete.html#delete-relationship">Delete Relationship</a></li>
</ul>
<h2 id="delete-relationship"><a class="header" href="#delete-relationship">Delete relationship</a></h2>
<p>The GraphQL</p>
<pre><code>mutation {
  OrganizationMembersDelete(
    input: {
      MATCH: {
        src: { Organization: { name: { EQ: &quot;Warpforge&quot; } } }
        dst: { User: { email: { EQ: &quot;constantine@example.com&quot; } } }
      }
    }
  )
}
</code></pre>
<p>The output is as follows.</p>
<pre><code class="language-json">{
  &quot;data&quot;: {
    &quot;OrganizationMembersDelete&quot;: 1
  }
}
</code></pre>
<div style="break-before: page; page-break-before: always;"></div><h1 id="engine-features"><a class="header" href="#engine-features">Engine Features</a></h1>
<p>As shown in the previous chapters, Warpgrapher offers a considerable amount of power and flexibility out of the box. With a configuration describing a data model and a little bit of server integration code, it is possible to stand up a fully functional GraphQL service, with automatically generated CRUD operations for nodes and relationships. The recursive structure of the schema allows for single queries and mutations over arbitrarily deep sub-graphs.</p>
<p>However, these features by themselves are still not enough for a production service. A robust production system will doubtless need to validate inputs. It will likely need to conduct authorization checks on requests as a security control. It will need to include business logic that does calculations over existing data or launches additional workflows. This chapter discusses the extensibility features of the Warpgrapher engine make it possible to build a real GraphQL API service on top of the foundation that Warpgrapher provides.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="static-endpoints"><a class="header" href="#static-endpoints">Static Endpoints</a></h1>
<p>Warpgrapher includes built-in static endpoints that provide useful information or functionality. Built-in static endpoints names are preceded by <code>_</code>.</p>
<h2 id="version"><a class="header" href="#version">Version</a></h2>
<p>If the <code>Engine</code> is built with an explicit version:</p>
<pre><pre class="playground"><code class="language-rust">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>let mut server: Engine&lt;()&gt; = Engine::new(config, db)
    .with_version(&quot;0.1.0&quot;.to_string())
    .build();
<span class="boring">}
</span></code></pre></pre>
<p>the version value can be accessed via the <code>_version</code> endpoint:</p>
<pre><code>query {
    _version
}
</code></pre>
<pre><code class="language-json">{
  &quot;data&quot;: {
    &quot;_version&quot;: &quot;0.1.0&quot;
  }
}
</code></pre>
<p>If the server is not configured with an explicit version, the <code>_version</code> endpoint will return <code>null</code>:</p>
<pre><code class="language-json">{
  &quot;data&quot;: {
    &quot;_version&quot;: null
  }
}
</code></pre>
<div style="break-before: page; page-break-before: always;"></div><h1 id="defined-endpoints"><a class="header" href="#defined-endpoints">Defined Endpoints</a></h1>
<p>In addition to the CRUD endpoints auto-generated for each type, Warpgrapher provides the ability to define additional custom endpoints. </p>
<h2 id="configuration-1"><a class="header" href="#configuration-1">Configuration</a></h2>
<p>The schema for an endpoint entry in the Warpgrapher configuration is as follows.</p>
<pre><code>endpoints:
  - name: String
    class: String         # /Mutation | Query/
    input:                # null if there is no input parameter
      type: String
      list: Boolean
      required: Boolean
    output:               # null if there is no input parameter
      type: String
      list: Boolean       # defaults to false
      required: Boolean   # defaults to false
</code></pre>
<p>The <code>name</code> of the endpoint will be used later as the key to a hash of endpoint resolution fuctions. It uniquely identified this endpoint. The <code>class</code> attribute tells Warpgrapher whether this endpoint belongs under the root query or root mutation object. The convention is that any operation with side effects, modifying the persistent data store, should be a mutation. Read-only operations are queries.  The <code>input</code> attribute allows specification of an input to the endpoint function. The input type may be a scalar GraphQL type -- <code>Boolean</code>, <code>Float</code>, <code>ID</code>, <code>Int</code>, or <code>String</code> -- or it may be a type defined elsewhere in the <code>model</code> section of the Warpgrapher configuration.  The <code>list</code> determines whether the input is actually a list of that type rather than a singular instance.  If the <code>required</code> attribute is true, the input is required.  If <code>false</code>, the input is optional.  The <code>output</code> attribute describes the value returned by the custom endpoint. It has fields similar to <code>input</code>, in that it includes <code>type</code>, <code>lsit</code>, and <code>required</code> attributes.</p>
<p>The following configuration defines a custom endpoints, <code>TopIssue</code>.</p>
<pre><code class="language-yaml">version: 1
model: 
 - name: Issue
   props: 
    - name: name
      type: String 
    - name: points
      type: Int 
endpoints:
  - name: TopIssue
    class: Query
    input: null
    output:
      type: Issue
</code></pre>
<h2 id="implementation"><a class="header" href="#implementation">Implementation</a></h2>
<p>To implement the custom endpoint, a resolver function is defined, as follows. In this example, the function just puts together a static response and resolves it. A real system would like do some comparison of nodes and relationships to determine the top issue, and dynamically return that record.</p>
<pre><pre class="playground"><code class="language-rust">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>// endpoint returning a list of `Issue` nodes
fn resolve_top_issue(facade: ResolverFacade&lt;AppRequestContext&gt;) -&gt; BoxFuture&lt;ExecutionResult&gt; {
    Box::pin(async move {
        let top_issue = facade.node(
            &quot;Issue&quot;,
            hashmap! {
                &quot;name&quot;.to_string() =&gt; Value::from(&quot;Learn more rust&quot;.to_string()),
                &quot;points&quot;.to_string() =&gt; Value::from(Into::&lt;i64&gt;::into(5))
            },
        );

        facade.resolve_node(&amp;top_issue).await
    })
}
<span class="boring">}
</span></code></pre></pre>
<h2 id="add-resolvers-to-the-warpgrapher-engine"><a class="header" href="#add-resolvers-to-the-warpgrapher-engine">Add Resolvers to the Warpgrapher Engine</a></h2>
<p>To add the custom endpoint resolver to the engine, it must be associated with the name the endpoint was given in the configuration above. The example code below creates a <code>HashMap</code> to map from the custom endpoint name and the implementing function. That map is then passed to the <code>Engine</code> when it is created.</p>
<pre><pre class="playground"><code class="language-rust">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>    // define resolvers
    let mut resolvers = Resolvers::&lt;AppRequestContext&gt;::new();
    resolvers.insert(&quot;TopIssue&quot;.to_string(), Box::new(resolve_top_issue));

    // create warpgrapher engine
    let engine: Engine&lt;AppRequestContext&gt; = Engine::new(config, db)
        .with_resolvers(resolvers)
        .build()
        .expect(&quot;Failed to build engine&quot;);
<span class="boring">}
</span></code></pre></pre>
<h2 id="example-of-calling-the-endpoint"><a class="header" href="#example-of-calling-the-endpoint">Example of Calling the Endpoint</a></h2>
<p>The code below calls the endine with a query that exercises the custom endpoint.</p>
<pre><code>    let query = &quot;
        query {
            TopIssue {
                name
                points
            }
        }
    &quot;
    .to_string();
    let metadata = HashMap::new();
    let result = engine.execute(query, None, metadata).await.unwrap();
</code></pre>
<h2 id="full-example-source"><a class="header" href="#full-example-source">Full Example Source</a></h2>
<p>See below for the full code for the example above.</p>
<pre><code>use maplit::hashmap;
use std::collections::HashMap;
use std::convert::TryFrom;
use warpgrapher::engine::config::Configuration;
use warpgrapher::engine::context::RequestContext;
use warpgrapher::engine::database::cypher::CypherEndpoint;
use warpgrapher::engine::database::DatabaseEndpoint;
use warpgrapher::engine::resolvers::{ExecutionResult, ResolverFacade, Resolvers};
use warpgrapher::engine::value::Value;
use warpgrapher::juniper::BoxFuture;
use warpgrapher::Engine;

static CONFIG: &amp;str = &quot;
version: 1
model: 
 - name: Issue
   props: 
    - name: name
      type: String 
    - name: points
      type: Int 
endpoints:
  - name: TopIssue
    class: Query
    input: null
    output:
      type: Issue
&quot;;

#[derive(Clone, Debug)]
struct AppRequestContext {}

impl RequestContext for AppRequestContext {
    type DBEndpointType = CypherEndpoint;
    fn new() -&gt; AppRequestContext {
        AppRequestContext {}
    }
}

// endpoint returning a list of `Issue` nodes
fn resolve_top_issue(facade: ResolverFacade&lt;AppRequestContext&gt;) -&gt; BoxFuture&lt;ExecutionResult&gt; {
    Box::pin(async move {
        let top_issue = facade.node(
            &quot;Issue&quot;,
            hashmap! {
                &quot;name&quot;.to_string() =&gt; Value::from(&quot;Learn more rust&quot;.to_string()),
                &quot;points&quot;.to_string() =&gt; Value::from(Into::&lt;i64&gt;::into(5))
            },
        );

        facade.resolve_node(&amp;top_issue).await
    })
}

#[tokio::main]
async fn main() {
    // parse warpgrapher config
    let config = Configuration::try_from(CONFIG.to_string()).expect(&quot;Failed to parse CONFIG&quot;);

    // define database endpoint
    let db = CypherEndpoint::from_env()
        .expect(&quot;Failed to parse cypher endpoint from environment&quot;)
        .pool()
        .await
        .expect(&quot;Failed to create cypher database pool&quot;);

    // define resolvers
    let mut resolvers = Resolvers::&lt;AppRequestContext&gt;::new();
    resolvers.insert(&quot;TopIssue&quot;.to_string(), Box::new(resolve_top_issue));

    // create warpgrapher engine
    let engine: Engine&lt;AppRequestContext&gt; = Engine::new(config, db)
        .with_resolvers(resolvers)
        .build()
        .expect(&quot;Failed to build engine&quot;);

    // create new project
    let query = &quot;
        query {
            TopIssue {
                name
                points
            }
        }
    &quot;
    .to_string();
    let metadata = HashMap::new();
    let result = engine.execute(query, None, metadata).await.unwrap();

    // verify result
    println!(&quot;result: {:#?}&quot;, result);
}
</code></pre>
<div style="break-before: page; page-break-before: always;"></div><h1 id="dynamic-props"><a class="header" href="#dynamic-props">Dynamic Props</a></h1>
<p>When Warpgrapher auto-generates a CRUD endpoint, the values of Node and Relationship properties are retreived from the database and returned in a query. In some cases, however, it may be necessary to perform real-time computations to derive the value of a prop. We call these type of properties &quot;dynamic properties&quot;, and Warpgrapher provides a mechanism to execute custom logic to resolve their values.</p>
<h2 id="configuration-2"><a class="header" href="#configuration-2">Configuration</a></h2>
<p>In the configuration below, <code>points</code> is a dynamic property on the <code>Project</code> type. It has an associated resolver name of <code>resolve_project_points</code>. That name will be used later to connect the Rust resolver function to this entry in the configuration.</p>
<pre><code class="language-rust no_run noplayground">model: 
 - name: Project
   props: 
    - name: name
      type: String 
    - name: points
      type: Int
      resolver: resolve_project_points
&quot;;
</code></pre>
<h2 id="implementation-1"><a class="header" href="#implementation-1">Implementation</a></h2>
<p>The implementation below defines the resolver. In this example, the resolver simply returns a constant value. In a real system, the implementation might retrieve records and do some calculation to total up a number of points associated with a project.</p>
<pre><code class="language-rust no_run noplayground">fn resolve_project_points(facade: ResolverFacade&lt;AppRequestContext&gt;) -&gt; BoxFuture&lt;ExecutionResult&gt; {
    Box::pin(async move {
        // compute value
        let points = 5;

        facade.resolve_scalar(points)
    })
}
</code></pre>
<h2 id="add-resolvers-to-the-engine"><a class="header" href="#add-resolvers-to-the-engine">Add Resolvers to the Engine</a></h2>
<p>The code in the snippet below adds the resolver function to a map. They key is the name for the custom resolver that was used in the configuration, above. The map is then passed to the Wargrapher engine, allowing the engine to find the resolver function when the dynamic property must be resolved.</p>
<pre><code class="language-rust no_run noplayground">    let mut resolvers = Resolvers::&lt;AppRequestContext&gt;::new();
    resolvers.insert(
        &quot;resolve_project_points&quot;.to_string(),
        Box::new(resolve_project_points),
    );

    // create warpgrapher engine
    let engine: Engine&lt;AppRequestContext&gt; = Engine::new(config, db)
        .with_resolvers(resolvers)
        .build()
        .expect(&quot;Failed to build engine&quot;);

</code></pre>
<h2 id="example-api-call"><a class="header" href="#example-api-call">Example API Call</a></h2>
<p>The following GraphQL query uses the dynamic resolver defined above.</p>
<pre><code class="language-rust no_run noplayground">    // create new project
    let query = &quot;
        mutation {
            ProjectCreate(input: {
                name: \&quot;Project1\&quot;
            }) {
                id
                points
            }
        }
    &quot;
    .to_string();
    let metadata = HashMap::new();
    let result = engine.execute(query, None, metadata).await.unwrap();
</code></pre>
<h2 id="full-example-source-1"><a class="header" href="#full-example-source-1">Full Example Source</a></h2>
<p>See below for the full source code to the example above.</p>
<pre><code class="language-rust no_run noplayground">use std::collections::HashMap;
use std::convert::TryFrom;
use warpgrapher::engine::config::Configuration;
use warpgrapher::engine::context::RequestContext;
use warpgrapher::engine::database::cypher::CypherEndpoint;
use warpgrapher::engine::database::DatabaseEndpoint;
use warpgrapher::engine::resolvers::{ExecutionResult, ResolverFacade, Resolvers};
use warpgrapher::juniper::BoxFuture;
use warpgrapher::Engine;

static CONFIG: &amp;str = &quot;
version: 1
model: 
 - name: Project
   props: 
    - name: name
      type: String 
    - name: points
      type: Int
      resolver: resolve_project_points
&quot;;

#[derive(Clone, Debug)]
struct AppRequestContext {}

impl RequestContext for AppRequestContext {
    type DBEndpointType = CypherEndpoint;
    fn new() -&gt; AppRequestContext {
        AppRequestContext {}
    }
}

fn resolve_project_points(facade: ResolverFacade&lt;AppRequestContext&gt;) -&gt; BoxFuture&lt;ExecutionResult&gt; {
    Box::pin(async move {
        // compute value
        let points = 5;

        facade.resolve_scalar(points)
    })
}

#[tokio::main]
async fn main() {
    // parse warpgrapher config
    let config = Configuration::try_from(CONFIG.to_string()).expect(&quot;Failed to parse CONFIG&quot;);

    // define database endpoint
    let db = CypherEndpoint::from_env()
        .expect(&quot;Failed to parse cypher endpoint from environment&quot;)
        .pool()
        .await
        .expect(&quot;Failed to create cypher database pool&quot;);

    // define resolvers
    let mut resolvers = Resolvers::&lt;AppRequestContext&gt;::new();
    resolvers.insert(
        &quot;resolve_project_points&quot;.to_string(),
        Box::new(resolve_project_points),
    );

    // create warpgrapher engine
    let engine: Engine&lt;AppRequestContext&gt; = Engine::new(config, db)
        .with_resolvers(resolvers)
        .build()
        .expect(&quot;Failed to build engine&quot;);

    // create new project
    let query = &quot;
        mutation {
            ProjectCreate(input: {
                name: \&quot;Project1\&quot;
            }) {
                id
                points
            }
        }
    &quot;
    .to_string();
    let metadata = HashMap::new();
    let result = engine.execute(query, None, metadata).await.unwrap();

    // verify result
    assert_eq!(
        &quot;123456&quot;,
        result
            .get(&quot;data&quot;)
            .unwrap()
            .get(&quot;GetEnvironment&quot;)
            .unwrap()
            .as_str()
            .unwrap(),
    );
}
</code></pre>
<div style="break-before: page; page-break-before: always;"></div><h1 id="dynamic-relationships"><a class="header" href="#dynamic-relationships">Dynamic Relationships</a></h1>
<p>Dynamic relationships are similiar to dynamic properties, but returning dynamically calculated relationships to other nodes as opposed to individual properties.</p>
<h2 id="configuration-3"><a class="header" href="#configuration-3">Configuration</a></h2>
<p>The configuration below includes a dynamic resolver called <code>resolve_project_top_contributor</code> for the <code>top_contributor</code> relationship. That resolver name will be used later to associate a Rust function to carry out the dynamic resolution.</p>
<pre><code class="language-rust no_run noplayground">version: 1
model: 
 - name: User
   props:
    - name: name
      type: String
 - name: Project
   props: 
    - name: name
      type: String 
   rels:
     - name: top_contributor
       nodes: [User]
       resolver: resolve_project_top_contributor
</code></pre>
<h2 id="implementation-2"><a class="header" href="#implementation-2">Implementation</a></h2>
<p>The next step is to define the custom resolution function in Rust. In this example, the custom relationship resolver creates a hard-coded node and relationship. In a real system, the function might load records and do some calculation or analytic logic to determine who is the top contributor to a project, and then return that user.</p>
<pre><code class="language-rust no_run noplayground">fn resolve_project_top_contributor(
    facade: ResolverFacade&lt;AppRequestContext&gt;,
) -&gt; BoxFuture&lt;ExecutionResult&gt; {
    Box::pin(async move {
        // create dynamic dst node
        let mut top_contributor_props = HashMap::&lt;String, Value&gt;::new();
        top_contributor_props.insert(
            &quot;id&quot;.to_string(),
            Value::from(Uuid::new_v4().to_hyphenated().to_string()),
        );
        top_contributor_props.insert(&quot;name&quot;.to_string(), Value::from(&quot;user0&quot;.to_string()));
        let top_contributor = facade.node(&quot;User&quot;, top_contributor_props);

        // create dynamic rel
        let rel_id = &quot;1234567890&quot;.to_string();
        let top_contributor_rel = facade.create_rel_with_dst_node(
            Value::from(rel_id),
            &quot;topdev&quot;,
            HashMap::new(),
            top_contributor,
        )?;
</code></pre>
<h2 id="add-the-resolver-to-the-engine"><a class="header" href="#add-the-resolver-to-the-engine">Add the Resolver to the Engine</a></h2>
<p>The resolver is added to a map associated with the name used in the configuration, above. The map is then passed to the Warpgrapher engine. This allows the engine to find the Rust function implementing the custom resolver when it is needed.</p>
<pre><code class="language-rust no_run noplayground">        .await
        .expect(&quot;Failed to create cypher database pool&quot;);

    // define resolvers
    let mut resolvers = Resolvers::&lt;AppRequestContext&gt;::new();
    resolvers.insert(
        &quot;resolve_project_top_contributor&quot;.to_string(),
        Box::new(resolve_project_top_contributor),
    );

    // create warpgrapher engine
    let engine: Engine&lt;AppRequestContext&gt; = Engine::new(config, db)
</code></pre>
<h2 id="example-api-call-1"><a class="header" href="#example-api-call-1">Example API Call</a></h2>
<p>The following GraphQL query uses the dynamic resolver defined above.</p>
<pre><code class="language-rust no_run noplayground">        .build()
        .expect(&quot;Failed to build engine&quot;);

    // create new project
    let query = &quot;
        mutation {
            ProjectCreate(input: {
                name: \&quot;Project1\&quot;
            }) {
                id
                top_contributor {
                    dst {
                        ... on User {
                            id
                            name
                        }
                    }
                }
            }
        }
</code></pre>
<h2 id="full-example-source-2"><a class="header" href="#full-example-source-2">Full Example Source</a></h2>
<p>See below for the full source code to the example above.</p>
<pre><code class="language-rust no_run noplayground">use std::collections::HashMap;
use std::convert::TryFrom;
use uuid::Uuid;
use warpgrapher::engine::config::Configuration;
use warpgrapher::engine::context::RequestContext;
use warpgrapher::engine::database::cypher::CypherEndpoint;
use warpgrapher::engine::database::DatabaseEndpoint;
use warpgrapher::engine::resolvers::{ExecutionResult, ResolverFacade, Resolvers};
use warpgrapher::engine::value::Value;
use warpgrapher::juniper::BoxFuture;
use warpgrapher::Engine;

static CONFIG: &amp;str = &quot;
version: 1
model: 
 - name: User
   props:
    - name: name
      type: String
 - name: Project
   props: 
    - name: name
      type: String 
   rels:
     - name: top_contributor
       nodes: [User]
       resolver: resolve_project_top_contributor
&quot;;

#[derive(Clone, Debug)]
struct AppRequestContext {}

impl RequestContext for AppRequestContext {
    type DBEndpointType = CypherEndpoint;
    fn new() -&gt; AppRequestContext {
        AppRequestContext {}
    }
}

fn resolve_project_top_contributor(
    facade: ResolverFacade&lt;AppRequestContext&gt;,
) -&gt; BoxFuture&lt;ExecutionResult&gt; {
    Box::pin(async move {
        // create dynamic dst node
        let mut top_contributor_props = HashMap::&lt;String, Value&gt;::new();
        top_contributor_props.insert(
            &quot;id&quot;.to_string(),
            Value::from(Uuid::new_v4().to_hyphenated().to_string()),
        );
        top_contributor_props.insert(&quot;name&quot;.to_string(), Value::from(&quot;user0&quot;.to_string()));
        let top_contributor = facade.node(&quot;User&quot;, top_contributor_props);

        // create dynamic rel
        let rel_id = &quot;1234567890&quot;.to_string();
        let top_contributor_rel = facade.create_rel_with_dst_node(
            Value::from(rel_id),
            &quot;topdev&quot;,
            HashMap::new(),
            top_contributor,
        )?;

        facade.resolve_rel(&amp;top_contributor_rel).await
    })
}

#[tokio::main]
async fn main() {
    // parse warpgrapher config
    let config = Configuration::try_from(CONFIG.to_string()).expect(&quot;Failed to parse CONFIG&quot;);

    // define database endpoint
    let db = CypherEndpoint::from_env()
        .expect(&quot;Failed to parse cypher endpoint from environment&quot;)
        .pool()
        .await
        .expect(&quot;Failed to create cypher database pool&quot;);

    // define resolvers
    let mut resolvers = Resolvers::&lt;AppRequestContext&gt;::new();
    resolvers.insert(
        &quot;resolve_project_top_contributor&quot;.to_string(),
        Box::new(resolve_project_top_contributor),
    );

    // create warpgrapher engine
    let engine: Engine&lt;AppRequestContext&gt; = Engine::new(config, db)
        .with_resolvers(resolvers)
        .build()
        .expect(&quot;Failed to build engine&quot;);

    // create new project
    let query = &quot;
        mutation {
            ProjectCreate(input: {
                name: \&quot;Project1\&quot;
            }) {
                id
                top_contributor {
                    dst {
                        ... on User {
                            id
                            name
                        }
                    }
                }
            }
        }
    &quot;
    .to_string();
    let metadata = HashMap::new();
    let result = engine.execute(query, None, metadata).await.unwrap();

    // verify result
    println!(&quot;result: {:#?}&quot;, result);
}
</code></pre>
<div style="break-before: page; page-break-before: always;"></div><h1 id="request-context"><a class="header" href="#request-context">Request Context</a></h1>
<p>In some cases, it's desirable to pass custom state information from your application into the Warpgrapher request cycle, so that your custom resolvers can make use of that information. The request context makes this passing of state possible.</p>
<h2 id="define-the-requestcontext"><a class="header" href="#define-the-requestcontext">Define the RequestContext</a></h2>
<p>Every system using Warpgrapher defines a struct that implements <code>RequestContext</code>. In addition to implementing the trait, that struct is free to carry additional state information. However, the context must implement <code>Clone</code>, <code>Debug</code>, <code>Sync</code>, <code>Send</code>, as well as Warpgrapher's <code>RequestContext</code> trait. See the code snippet below for an example.</p>
<pre><code class="language-rust no_run noplayground">#[derive(Clone, Debug)]
struct AppRequestContext {
    request_id: String,
}

impl RequestContext for AppRequestContext {
    type DBEndpointType = CypherEndpoint;
    fn new() -&gt; AppRequestContext {
        // generate random request id
        let request_id = &quot;1234&quot;.to_string();
        AppRequestContext { request_id }
    }
}
</code></pre>
<h2 id="engine-type-parameter"><a class="header" href="#engine-type-parameter">Engine Type Parameter</a></h2>
<p>The struct that implements <code>RequestContext</code> is passed to the <code>Engine</code> as a type parameter, as shown in the code snippet below.</p>
<pre><code class="language-rust no_run noplayground">    // create warpgrapher engine
    let engine: Engine&lt;AppRequestContext&gt; = Engine::new(config, db)
        .with_resolvers(resolvers)
        .build()
        .expect(&quot;Failed to build engine&quot;);
</code></pre>
<h2 id="access-the-context"><a class="header" href="#access-the-context">Access the Context</a></h2>
<p>Once passed to the <code>Engine</code>, the struct implementing <code>RequestContext</code> is available to functions that implement custom endpoints and resolvers, as shown in the snippet below.</p>
<pre><code class="language-rust no_run noplayground">fn resolve_echo_request(facade: ResolverFacade&lt;AppRequestContext&gt;) -&gt; BoxFuture&lt;ExecutionResult&gt; {
    Box::pin(async move {
        let request_context = facade.request_context().unwrap();
        let request_id = request_context.request_id.clone();
        facade.resolve_scalar(format!(&quot;echo! (request_id: {})&quot;, request_id))
    })
}
</code></pre>
<h2 id="full-example-source-3"><a class="header" href="#full-example-source-3">Full Example Source</a></h2>
<pre><code class="language-rust no_run noplayground">use std::collections::HashMap;
use std::convert::TryFrom;
use warpgrapher::engine::config::Configuration;
use warpgrapher::engine::context::RequestContext;
use warpgrapher::engine::database::cypher::CypherEndpoint;
use warpgrapher::engine::database::DatabaseEndpoint;
use warpgrapher::engine::resolvers::{ExecutionResult, ResolverFacade, Resolvers};
use warpgrapher::juniper::BoxFuture;
use warpgrapher::Engine;

static CONFIG: &amp;str = &quot;
version: 1
model:
  - name: User
    props:
      - name: email
        type: String
endpoints:
  - name: EchoRequest
    class: Query
    input: null
    output: 
      type: String
&quot;;

#[derive(Clone, Debug)]
struct AppRequestContext {
    request_id: String,
}

impl RequestContext for AppRequestContext {
    type DBEndpointType = CypherEndpoint;
    fn new() -&gt; AppRequestContext {
        // generate random request id
        let request_id = &quot;1234&quot;.to_string();
        AppRequestContext { request_id }
    }
}

fn resolve_echo_request(facade: ResolverFacade&lt;AppRequestContext&gt;) -&gt; BoxFuture&lt;ExecutionResult&gt; {
    Box::pin(async move {
        let request_context = facade.request_context().unwrap();
        let request_id = request_context.request_id.clone();
        facade.resolve_scalar(format!(&quot;echo! (request_id: {})&quot;, request_id))
    })
}

#[tokio::main]
async fn main() {
    // parse warpgrapher config
    let config = Configuration::try_from(CONFIG.to_string()).expect(&quot;Failed to parse CONFIG&quot;);

    // define database endpoint
    let db = CypherEndpoint::from_env()
        .expect(&quot;Failed to parse cypher endpoint from environment&quot;)
        .pool()
        .await
        .expect(&quot;Failed to create cypher database pool&quot;);

    // define resolvers
    let mut resolvers = Resolvers::&lt;AppRequestContext&gt;::new();
    resolvers.insert(&quot;EchoRequest&quot;.to_string(), Box::new(resolve_echo_request));

    // create warpgrapher engine
    let engine: Engine&lt;AppRequestContext&gt; = Engine::new(config, db)
        .with_resolvers(resolvers)
        .build()
        .expect(&quot;Failed to build engine&quot;);

    // execute query on `GetEnvironment` endpoint
    let query = &quot;
        query {
            EchoRequest
        }
    &quot;
    .to_string();
    let metadata = HashMap::new();
    let result = engine.execute(query, None, metadata).await.unwrap();

    // verify result
    println!(&quot;result: {:#?}&quot;, result);
    assert_eq!(
        &quot;echo! (request_id: 1234)&quot;,
        result
            .get(&quot;data&quot;)
            .unwrap()
            .get(&quot;EchoRequest&quot;)
            .unwrap()
            .as_str()
            .unwrap(),
    );
}
</code></pre>
<div style="break-before: page; page-break-before: always;"></div><h1 id="input-validation"><a class="header" href="#input-validation">Input Validation</a></h1>
<p>In many cases, it's necessary to ensure that inputs are valid. What constitutes a valid input is up to the application, but it may mean that values have to be less than a certain length, within a certain range, and/or include or exclude certain characters. Warpgrapher makes it possible to write custom validation functions to reject invalid inputs.</p>
<h2 id="configuration-4"><a class="header" href="#configuration-4">Configuration</a></h2>
<p>In the configuration snippet below, the <code>name</code> property has a <code>validator</code> field with the name <code>NameValidator</code>. The <code>NameValidator</code> string will be used later to connect the Rust function with this definition in the schema.</p>
<pre><code class="language-rust no_run noplayground">version: 1
model:
<span class="boring">  User
</span>  - name: User
    props:
      - name: name
        type: String
        required: true
        validator: NameValidator
</code></pre>
<h2 id="implementation-3"><a class="header" href="#implementation-3">Implementation</a></h2>
<p>The implementation below defines the input validation function itself. The function is relatively simple, rejecting the input if the name is &quot;KENOBI&quot;.  All other names are accepted.</p>
<pre><code class="language-rust no_run noplayground">fn name_validator(value: &amp;Value) -&gt; Result&lt;(), Error&gt; {
    if let Value::Map(m) = value {
        if let Some(Value::String(name)) = m.get(&quot;name&quot;) {
            if name == &quot;KENOBI&quot; {
                Err(Error::ValidationFailed {
                    message: format!(
                        &quot;Input validator for {field_name} failed. Cannot be named KENOBI&quot;,
                        field_name = &quot;name&quot;
                    ),
                })
            } else {
                Ok(())
            }
        } else {
            Err(Error::ValidationFailed {
                message: format!(
                    &quot;Input validator for {field_name} failed.&quot;,
                    field_name = &quot;name&quot;
                ),
            })
        }
    } else {
        Err(Error::ValidationFailed {
            message: format!(
                &quot;Input validator for {field_name} failed.&quot;,
                field_name = &quot;name&quot;
            ),
        })
    }
}
</code></pre>
<h2 id="add-validators-to-the-engine"><a class="header" href="#add-validators-to-the-engine">Add Validators to the Engine</a></h2>
<p>The validators, such as the one defined above, are packaged into a map from the name(s) used in the configuration to the Rust functions. The map is then provided to the Warpgrapher <code>Engine</code> as the engine is built.</p>
<pre><code class="language-rust no_run noplayground">    // load validators
    let mut validators: Validators = Validators::new();
    validators.insert(&quot;NameValidator&quot;.to_string(), Box::new(name_validator));

    // create warpgrapher engine
    let engine: Engine&lt;AppRequestContext&gt; = Engine::new(config, db)
        .with_validators(validators.clone())
        .build()
        .expect(&quot;Failed to build engine&quot;);
</code></pre>
<h2 id="example-api-call-2"><a class="header" href="#example-api-call-2">Example API Call</a></h2>
<p>The follow example API call invokes the validator defined above.</p>
<pre><code class="language-rust no_run noplayground">    let query = &quot;
        mutation {
            UserCreate(input: {
                name: \&quot;KENOBI\&quot;
            }) {
                id
                name
            }
        }
    &quot;
    .to_string();
    let metadata = HashMap::new();
    let result = engine.execute(query, None, metadata).await.unwrap();
</code></pre>
<h2 id="full-example-source-4"><a class="header" href="#full-example-source-4">Full Example Source</a></h2>
<p>See below for the full source code to the example above.</p>
<pre><code class="language-rust no_run noplayground">use std::collections::HashMap;
use std::convert::TryFrom;
use warpgrapher::engine::config::Configuration;
use warpgrapher::engine::context::RequestContext;
use warpgrapher::engine::database::cypher::CypherEndpoint;
use warpgrapher::engine::database::DatabaseEndpoint;
use warpgrapher::engine::validators::Validators;
use warpgrapher::engine::value::Value;
use warpgrapher::{Engine, Error};

static CONFIG: &amp;str = &quot;
version: 1
model:
<span class="boring">  User
</span>  - name: User
    props:
      - name: name
        type: String
        required: true
        validator: NameValidator
&quot;;

#[derive(Clone, Debug)]
struct AppRequestContext {}

impl RequestContext for AppRequestContext {
    type DBEndpointType = CypherEndpoint;
    fn new() -&gt; AppRequestContext {
        AppRequestContext {}
    }
}

fn name_validator(value: &amp;Value) -&gt; Result&lt;(), Error&gt; {
    if let Value::Map(m) = value {
        if let Some(Value::String(name)) = m.get(&quot;name&quot;) {
            if name == &quot;KENOBI&quot; {
                Err(Error::ValidationFailed {
                    message: format!(
                        &quot;Input validator for {field_name} failed. Cannot be named KENOBI&quot;,
                        field_name = &quot;name&quot;
                    ),
                })
            } else {
                Ok(())
            }
        } else {
            Err(Error::ValidationFailed {
                message: format!(
                    &quot;Input validator for {field_name} failed.&quot;,
                    field_name = &quot;name&quot;
                ),
            })
        }
    } else {
        Err(Error::ValidationFailed {
            message: format!(
                &quot;Input validator for {field_name} failed.&quot;,
                field_name = &quot;name&quot;
            ),
        })
    }
}

#[tokio::main]
async fn main() {
    // parse warpgrapher config
    let config = Configuration::try_from(CONFIG.to_string()).expect(&quot;Failed to parse CONFIG&quot;);

    // define database endpoint
    let db = CypherEndpoint::from_env()
        .expect(&quot;Failed to parse cypher endpoint from environment&quot;)
        .pool()
        .await
        .expect(&quot;Failed to create cypher database pool&quot;);

    // load validators
    let mut validators: Validators = Validators::new();
    validators.insert(&quot;NameValidator&quot;.to_string(), Box::new(name_validator));

    // create warpgrapher engine
    let engine: Engine&lt;AppRequestContext&gt; = Engine::new(config, db)
        .with_validators(validators.clone())
        .build()
        .expect(&quot;Failed to build engine&quot;);

    let query = &quot;
        mutation {
            UserCreate(input: {
                name: \&quot;KENOBI\&quot;
            }) {
                id
                name
            }
        }
    &quot;
    .to_string();
    let metadata = HashMap::new();
    let result = engine.execute(query, None, metadata).await.unwrap();

    println!(&quot;result: {:#?}&quot;, result);
}
</code></pre>
<div style="break-before: page; page-break-before: always;"></div><h1 id="event-handlers"><a class="header" href="#event-handlers">Event Handlers</a></h1>
<p>The earlier sections of the book covered a great many options for customizing the behavior of Warpgrapher, including input validation, request context, custom endpoints, and dynamic properties and relationships. Warpgrapher offers an additional API, the event handling API, to modify Warpgrapher's behavior at almost every point in the lifecycle of a request. Event handlers may be added before <code>Engine</code> creation, before or after request handling, and before or after nodes ore relationsihps are created, read, updated, or deleted. This section will introduce the event handling API using an extended example of implementing a very simple authorization model. Each data record will be owned by one user, and only that user is entitled to read or modify that record.</p>
<h2 id="configuration-5"><a class="header" href="#configuration-5">Configuration</a></h2>
<p>Unlike some of the other customization points in the Warpgrapher engine, no special configuration is required for event handlers. They are created and added to the <code>Engine</code> using only Rust code. The data model used for this section's example is as follows.</p>
<pre><code class="language-rust no_run noplayground">version: 1
model:
  - name: Record
    props:
      - name: content
        type: String
</code></pre>
<h2 id="implementation-4"><a class="header" href="#implementation-4">Implementation</a></h2>
<p>The example introduces four event hooks illustrating different lifecycle events. One event handler is set up for before the engine is built. It takes in the configuration and modifies it to insert an additional property allowing the system to track the owner of a given <code>Record</code>.  A second event handler runs before every request, inserting the current username into a request context so that the system can determine who is making a request, and thus whether that current user matches the ownership of the records being affected.  The remaining event handlers run after node read events and before node modification events, in order to enforce the access control rules.</p>
<h3 id="before-engine-build"><a class="header" href="#before-engine-build">Before Engine Build</a></h3>
<p>The following function is run before the engine is built. It takes in a mutable copy of the configuration to be used to set up Warpgrapher <code>Engine</code>. This allows before engine build event handlers to make any concievable modification to the configuration. They can add or remove endpoints, types, properties, relationships, dynamic resolvers, validation, or anything else that can be included in a configuration.</p>
<pre><code class="language-rust no_run noplayground">/// before_build_engine event hook
/// Adds owner meta fields to all types in the model (though in this example, there's only one,
/// the record type)
fn add_owner_field(config: &amp;mut Configuration) -&gt; Result&lt;(), Error&gt; {
    for t in config.model.iter_mut() {
        let mut_props: &amp;mut Vec&lt;Property&gt; = t.mut_props();
        mut_props.push(Property::new(
            &quot;owner&quot;.to_string(),
            UsesFilter::none(),
            &quot;String&quot;.to_string(),
            false,
            false,
            None,
            None,
        ));
    }
    Ok(())
}
</code></pre>
<p>In this example, the handler is iterating through the configuration, finding every type declared in the data model. To each type, the handler is adding a new owner property that will record the identity of the owner of the record. This will later be used to validate that only the owner can read and modify the data.</p>
<h3 id="before-request-processing"><a class="header" href="#before-request-processing">Before Request Processing</a></h3>
<p>The following event hook function is run before every request that is processed by the Warpgrapher engine. In a full system implementation, it would likely pull information from the <code>metadata</code> parameter, such as request headers like a JWT, that might be parsed to pull out user identity information. That data might then be used to look up a user profile in the database. In this case, the example simply hard-codes a username. It does, however, demonstrate the use of an application-specific request context as a means of passing data in for use by other event handlers or by custom resolvers.</p>
<pre><code class="language-rust no_run noplayground">/// This event handler executes at the beginning of every request and attempts to insert the
/// current user's profile into the request context.
fn insert_user_profile(
    mut rctx: Rctx,
    mut _ef: EventFacade&lt;Rctx&gt;,
    _metadata: HashMap&lt;String, String&gt;,
) -&gt; BoxFuture&lt;Result&lt;Rctx, Error&gt;&gt; {
    Box::pin(async move {
        // A real implementation would likely pull a user identity from an authentication token in
        // metadata, or use that token to look up a full user profile in a database. In this
        // example, the identify is hard-coded.
        rctx.username = &quot;user-from-JWT&quot;.to_string();
        Ok(rctx)
    })
}
</code></pre>
<h3 id="before-node-creation"><a class="header" href="#before-node-creation">Before Node Creation</a></h3>
<p>The <code>insert_owner</code> event hook is run prior to the creation of any new nodes. The <code>Value</code> passed to the function is the GraphQL input type in the form of a Warpgrapher <code>Value</code>. In this case, the function modifies the input value to insert an additional property, the owner of the node about to be created, which is set to be the username of the current user. </p>
<pre><code class="language-rust no_run noplayground">/// before_create event hook
/// Inserts an owner meta property into every new node containing the id of the creator
fn insert_owner(mut v: Value, ef: EventFacade&lt;'_, Rctx&gt;) -&gt; BoxFuture&lt;Result&lt;Value, Error&gt;&gt; {
    Box::pin(async move {
        if let CrudOperation::CreateNode(_) = ef.op() {
            if let Value::Map(ref mut input) = v {
                let user_name = ef
                    .context()
                    .request_context()
                    .expect(&quot;Expect context&quot;)
                    .username
                    .to_string();
                input.insert(&quot;owner&quot;.to_string(), Value::String(user_name));
            }
        }
        Ok(v)
    })
}
</code></pre>
<p>The modified input value is returned from the event hook, and when Warpgrapher continues executing the node creation operation, the owner property is included in the node creation operation, alongside all the other input properties.</p>
<h3 id="after-node-read"><a class="header" href="#after-node-read">After Node Read</a></h3>
<p>The <code>enforce_read_access</code> event hook, defined below, is set to run after each node read operation. The Rust function is passed a <code>Vec</code> of nodes that that were read. The event hook function iterates through the nodes that were read, pulling out their owner property. That owner property is compared with the current logged in username. If the two match, the node belongs to the user, and the node is retained in the results list.  If the two do not match, then the current logged in user is not the owner of the record, and the node is discarded from the results list without ever being passed back to the user.</p>
<pre><code class="language-rust no_run noplayground">/// after_read event hook
/// Filters the read nodes to those that are authorized to be read
fn enforce_read_access(
    mut nodes: Vec&lt;Node&lt;Rctx&gt;&gt;,
    ef: EventFacade&lt;'_, Rctx&gt;,
) -&gt; BoxFuture&lt;Result&lt;Vec&lt;Node&lt;Rctx&gt;&gt;, Error&gt;&gt; {
    Box::pin(async move {
        nodes.retain(|node| {
            let node_owner: String = node
                .fields()
                .get(&quot;owner&quot;)
                .unwrap()
                .clone()
                .try_into()
                .expect(&quot;Expect to find owner field.&quot;);

            node_owner
                == ef
                    .context()
                    .request_context()
                    .expect(&quot;Context expected&quot;)
                    .username
        });
        Ok(nodes)
    })
}
</code></pre>
<h3 id="before-node-update-and-delete"><a class="header" href="#before-node-update-and-delete">Before Node Update and Delete</a></h3>
<p>The <code>enforce_write_access</code> event hook, shown below, is set to run before each node update or delete operation. The Rust function is passed the <code>input</code> value that corresponds to the GraphQL schema <code>input</code> argument type for the update or delete operation. In this example implementation, the function executes the <code>MATCH</code> portion of the update or delete query, reading all the nodes that are intended to be modified. For each of the nodes read, the event handler tests whether the owner attribute is the current logged in username. If the two match, the node belongs to the current user, and it is kept in the result set. If the username does not match the owner property on the object, then the node is discarded.</p>
<p>Once the node list is filtered, the event handler constructs a new <code>MATCH</code> query that will match the unique identifiers of all the nodes remaining in the filtered list. This new <code>MATCH</code> query is returned from the event handler and used subsequently in Warpgrapher's automatically generated resolvers to do the update or deletion operation.</p>
<pre><code class="language-rust no_run noplayground">/// before_update event hook
/// Filters out nodes that the user is not authorized to modify
fn enforce_write_access(
    v: Value,
    mut ef: EventFacade&lt;'_, Rctx&gt;,
) -&gt; BoxFuture&lt;Result&lt;Value, Error&gt;&gt; {
    Box::pin(async move {
        if let Value::Map(mut m) = v.clone() {
            if let Some(input_match) = m.remove(&quot;MATCH&quot;) {
                let nodes = &amp;ef.read_nodes(&quot;Record&quot;, input_match, None).await?;

                // filter nodes that are authorized
                let filtered_node_ids: Vec&lt;Value&gt; = nodes
                    .iter()
                    .filter(|n| {
                        let node_owner: String =
                            n.fields().get(&quot;owner&quot;).unwrap().clone().try_into().unwrap();

                        node_owner
                            == ef
                                .context()
                                .request_context()
                                .expect(&quot;Expect context.&quot;)
                                .username
                    })
                    .map(|n| Ok(n.id()?.clone()))
                    .collect::&lt;Result&lt;Vec&lt;Value&gt;, Error&gt;&gt;()?;

                // replace MATCH input with filtered nodes
                m.insert(
                    &quot;MATCH&quot;.to_string(),
                    Value::Map(hashmap! {
                        &quot;id&quot;.to_string() =&gt; Value::Map(hashmap! {
                            &quot;IN&quot;.to_string() =&gt; Value::Array(filtered_node_ids)
                        })
                    }),
                );

                // return modified input
                Ok(Value::Map(m))
            } else {
                // Return original input unmodified
                Ok(v)
            }
        } else {
            // Return original input unmodified
            Ok(v)
        }
    })
}
</code></pre>
<p>Although not necessary for this use case, the event handler could have just east as easily modified the <code>SET</code> portion of the update query as the <code>MATCH</code>, in some way adjusting the values used to update an existing node.</p>
<h2 id="add-handlers-to-the-engine"><a class="header" href="#add-handlers-to-the-engine">Add Handlers to the Engine</a></h2>
<p>The event handlers are all added to an <code>EventHandlerBag</code> which is then passed to the Warpgrapher engine.  The registration function determines where in the life cycle the hook will be called, and in some cases, such as before and after node and relationship CRUD operation handlers, there are arguments to specify which nodes or relationships should be affected.</p>
<pre><code class="language-rust no_run noplayground">    let mut ehb = EventHandlerBag::new();
    ehb.register_before_request(insert_user_profile);
    ehb.register_before_engine_build(add_owner_field);
    ehb.register_before_node_create(vec![&quot;Record&quot;.to_string()], insert_owner);
    ehb.register_after_node_read(vec![&quot;Record&quot;.to_string()], enforce_read_access);
    ehb.register_before_node_update(vec![&quot;Record&quot;.to_string()], enforce_write_access);
    ehb.register_before_node_delete(vec![&quot;Record&quot;.to_string()], enforce_write_access);

    // create warpgrapher engine
    let engine: Engine&lt;Rctx&gt; = Engine::new(config, db)
        .with_event_handlers(ehb)
        .build()
        .expect(&quot;Failed to build engine&quot;);
</code></pre>
<h2 id="example-api-call-3"><a class="header" href="#example-api-call-3">Example API Call</a></h2>
<p>The following GraphQL query triggers at least the first several event handlers in the call. Other queries and mutations would be needed to exercise all of them.</p>
<pre><code class="language-rust no_run noplayground">    let query = &quot;
        mutation {
            RecordCreate(input: {
                content: \&quot;Test Content\&quot;
            }) {
                id
                name
            }
        }
    &quot;
    .to_string();
    let metadata = HashMap::new();
    let result = engine.execute(query, None, metadata).await.unwrap();
</code></pre>
<h2 id="full-example-source-5"><a class="header" href="#full-example-source-5">Full Example Source</a></h2>
<p>See below for the full source code to the example above.</p>
<pre><code class="language-rust no_run noplayground">use maplit::hashmap;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::convert::TryInto;
use warpgrapher::engine::config::{Configuration, Property, UsesFilter};
use warpgrapher::engine::context::RequestContext;
use warpgrapher::engine::database::cypher::CypherEndpoint;
use warpgrapher::engine::database::CrudOperation;
use warpgrapher::engine::database::DatabaseEndpoint;
use warpgrapher::engine::events::{EventFacade, EventHandlerBag};
use warpgrapher::engine::objects::Node;
use warpgrapher::engine::value::Value;
use warpgrapher::juniper::BoxFuture;
use warpgrapher::{Engine, Error};

static CONFIG: &amp;str = &quot;
version: 1
model:
  - name: Record
    props:
      - name: content
        type: String
&quot;;

#[derive(Clone, Debug)]
pub struct Rctx {
    pub username: String,
}

impl Rctx {}

impl RequestContext for Rctx {
    type DBEndpointType = CypherEndpoint;

    fn new() -&gt; Self {
        Rctx {
            username: String::new(),
        }
    }
}

/// This event handler executes at the beginning of every request and attempts to insert the
/// current user's profile into the request context.
fn insert_user_profile(
    mut rctx: Rctx,
    mut _ef: EventFacade&lt;Rctx&gt;,
    _metadata: HashMap&lt;String, String&gt;,
) -&gt; BoxFuture&lt;Result&lt;Rctx, Error&gt;&gt; {
    Box::pin(async move {
        // A real implementation would likely pull a user identity from an authentication token in
        // metadata, or use that token to look up a full user profile in a database. In this
        // example, the identify is hard-coded.
        rctx.username = &quot;user-from-JWT&quot;.to_string();
        Ok(rctx)
    })
}

/// before_build_engine event hook
/// Adds owner meta fields to all types in the model (though in this example, there's only one,
/// the record type)
fn add_owner_field(config: &amp;mut Configuration) -&gt; Result&lt;(), Error&gt; {
    for t in config.model.iter_mut() {
        let mut_props: &amp;mut Vec&lt;Property&gt; = t.mut_props();
        mut_props.push(Property::new(
            &quot;owner&quot;.to_string(),
            UsesFilter::none(),
            &quot;String&quot;.to_string(),
            false,
            false,
            None,
            None,
        ));
    }
    Ok(())
}

/// before_create event hook
/// Inserts an owner meta property into every new node containing the id of the creator
fn insert_owner(mut v: Value, ef: EventFacade&lt;'_, Rctx&gt;) -&gt; BoxFuture&lt;Result&lt;Value, Error&gt;&gt; {
    Box::pin(async move {
        if let CrudOperation::CreateNode(_) = ef.op() {
            if let Value::Map(ref mut input) = v {
                let user_name = ef
                    .context()
                    .request_context()
                    .expect(&quot;Expect context&quot;)
                    .username
                    .to_string();
                input.insert(&quot;owner&quot;.to_string(), Value::String(user_name));
            }
        }
        Ok(v)
    })
}

/// after_read event hook
/// Filters the read nodes to those that are authorized to be read
fn enforce_read_access(
    mut nodes: Vec&lt;Node&lt;Rctx&gt;&gt;,
    ef: EventFacade&lt;'_, Rctx&gt;,
) -&gt; BoxFuture&lt;Result&lt;Vec&lt;Node&lt;Rctx&gt;&gt;, Error&gt;&gt; {
    Box::pin(async move {
        nodes.retain(|node| {
            let node_owner: String = node
                .fields()
                .get(&quot;owner&quot;)
                .unwrap()
                .clone()
                .try_into()
                .expect(&quot;Expect to find owner field.&quot;);

            node_owner
                == ef
                    .context()
                    .request_context()
                    .expect(&quot;Context expected&quot;)
                    .username
        });
        Ok(nodes)
    })
}

/// before_update event hook
/// Filters out nodes that the user is not authorized to modify
fn enforce_write_access(
    v: Value,
    mut ef: EventFacade&lt;'_, Rctx&gt;,
) -&gt; BoxFuture&lt;Result&lt;Value, Error&gt;&gt; {
    Box::pin(async move {
        if let Value::Map(mut m) = v.clone() {
            if let Some(input_match) = m.remove(&quot;MATCH&quot;) {
                let nodes = &amp;ef.read_nodes(&quot;Record&quot;, input_match, None).await?;

                // filter nodes that are authorized
                let filtered_node_ids: Vec&lt;Value&gt; = nodes
                    .iter()
                    .filter(|n| {
                        let node_owner: String =
                            n.fields().get(&quot;owner&quot;).unwrap().clone().try_into().unwrap();

                        node_owner
                            == ef
                                .context()
                                .request_context()
                                .expect(&quot;Expect context.&quot;)
                                .username
                    })
                    .map(|n| Ok(n.id()?.clone()))
                    .collect::&lt;Result&lt;Vec&lt;Value&gt;, Error&gt;&gt;()?;

                // replace MATCH input with filtered nodes
                m.insert(
                    &quot;MATCH&quot;.to_string(),
                    Value::Map(hashmap! {
                        &quot;id&quot;.to_string() =&gt; Value::Map(hashmap! {
                            &quot;IN&quot;.to_string() =&gt; Value::Array(filtered_node_ids)
                        })
                    }),
                );

                // return modified input
                Ok(Value::Map(m))
            } else {
                // Return original input unmodified
                Ok(v)
            }
        } else {
            // Return original input unmodified
            Ok(v)
        }
    })
}

#[tokio::main]
async fn main() {
    // parse warpgrapher config
    let config = Configuration::try_from(CONFIG.to_string()).expect(&quot;Failed to parse CONFIG&quot;);

    // define database endpoint
    let db = CypherEndpoint::from_env()
        .expect(&quot;Failed to parse cypher endpoint from environment&quot;)
        .pool()
        .await
        .expect(&quot;Failed to create cypher database pool&quot;);

    let mut ehb = EventHandlerBag::new();
    ehb.register_before_request(insert_user_profile);
    ehb.register_before_engine_build(add_owner_field);
    ehb.register_before_node_create(vec![&quot;Record&quot;.to_string()], insert_owner);
    ehb.register_after_node_read(vec![&quot;Record&quot;.to_string()], enforce_read_access);
    ehb.register_before_node_update(vec![&quot;Record&quot;.to_string()], enforce_write_access);
    ehb.register_before_node_delete(vec![&quot;Record&quot;.to_string()], enforce_write_access);

    // create warpgrapher engine
    let engine: Engine&lt;Rctx&gt; = Engine::new(config, db)
        .with_event_handlers(ehb)
        .build()
        .expect(&quot;Failed to build engine&quot;);

    let query = &quot;
        mutation {
            RecordCreate(input: {
                content: \&quot;Test Content\&quot;
            }) {
                id
                name
            }
        }
    &quot;
    .to_string();
    let metadata = HashMap::new();
    let result = engine.execute(query, None, metadata).await.unwrap();

    println!(&quot;result: {:#?}&quot;, result);
}
</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 type="text/javascript">
            window.playground_copyable = true;
        </script>


        <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>