Dynamic Props
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 "dynamic properties", and Warpgrapher provides a mechanism to execute custom logic to resolve their values.
Configuration
In the configuration below, points
is a dynamic property on the Project
type. It has an associated resolver name of resolve_project_points
. That name will be used later to connect the Rust resolver function to this entry in the configuration.
model:
- name: Project
props:
- name: name
type: String
- name: points
type: Int
resolver: resolve_project_points
";
Implementation
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.
fn resolve_project_points(facade: ResolverFacade<AppRequestContext>) -> BoxFuture<ExecutionResult> {
Box::pin(async move {
// compute value
let points = 5;
facade.resolve_scalar(points)
})
}
Add Resolvers to the Engine
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.
let mut resolvers = Resolvers::<AppRequestContext>::new();
resolvers.insert(
"resolve_project_points".to_string(),
Box::new(resolve_project_points),
);
// create warpgrapher engine
let engine: Engine<AppRequestContext> = Engine::new(config, db)
.with_resolvers(resolvers)
.build()
.expect("Failed to build engine");
Example API Call
The following GraphQL query uses the dynamic resolver defined above.
// create new project
let query = "
mutation {
ProjectCreate(input: {
name: \"Project1\"
}) {
id
points
}
}
"
.to_string();
let metadata = HashMap::new();
let result = engine.execute(query, None, metadata).await.unwrap();
Full Example Source
See below for the full source code to the example above.
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: &str = "
version: 1
model:
- name: Project
props:
- name: name
type: String
- name: points
type: Int
resolver: resolve_project_points
";
#[derive(Clone, Debug)]
struct AppRequestContext {}
impl RequestContext for AppRequestContext {
type DBEndpointType = CypherEndpoint;
fn new() -> AppRequestContext {
AppRequestContext {}
}
}
fn resolve_project_points(facade: ResolverFacade<AppRequestContext>) -> BoxFuture<ExecutionResult> {
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("Failed to parse CONFIG");
// define database endpoint
let db = CypherEndpoint::from_env()
.expect("Failed to parse cypher endpoint from environment")
.pool()
.await
.expect("Failed to create cypher database pool");
// define resolvers
let mut resolvers = Resolvers::<AppRequestContext>::new();
resolvers.insert(
"resolve_project_points".to_string(),
Box::new(resolve_project_points),
);
// create warpgrapher engine
let engine: Engine<AppRequestContext> = Engine::new(config, db)
.with_resolvers(resolvers)
.build()
.expect("Failed to build engine");
// create new project
let query = "
mutation {
ProjectCreate(input: {
name: \"Project1\"
}) {
id
points
}
}
"
.to_string();
let metadata = HashMap::new();
let result = engine.execute(query, None, metadata).await.unwrap();
// verify result
assert_eq!(
"123456",
result
.get("data")
.unwrap()
.get("GetEnvironment")
.unwrap()
.as_str()
.unwrap(),
);
}