Input Validation
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.
Configuration
In the configuration snippet below, the name
property has a validator
field with the name NameValidator
. The NameValidator
string will be used later to connect the Rust function with this definition in the schema.
version: 1
model:
User
- name: User
props:
- name: name
type: String
required: true
validator: NameValidator
Implementation
The implementation below defines the input validation function itself. The function is relatively simple, rejecting the input if the name is "KENOBI". All other names are accepted.
fn name_validator(value: &Value) -> Result<(), Error> {
if let Value::Map(m) = value {
if let Some(Value::String(name)) = m.get("name") {
if name == "KENOBI" {
Err(Error::ValidationFailed {
message: format!(
"Input validator for {field_name} failed. Cannot be named KENOBI",
field_name = "name"
),
})
} else {
Ok(())
}
} else {
Err(Error::ValidationFailed {
message: format!(
"Input validator for {field_name} failed.",
field_name = "name"
),
})
}
} else {
Err(Error::ValidationFailed {
message: format!(
"Input validator for {field_name} failed.",
field_name = "name"
),
})
}
}
Add Validators to the Engine
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 Engine
as the engine is built.
// load validators
let mut validators: Validators = Validators::new();
validators.insert("NameValidator".to_string(), Box::new(name_validator));
// create warpgrapher engine
let engine: Engine<AppRequestContext> = Engine::new(config, db)
.with_validators(validators.clone())
.build()
.expect("Failed to build engine");
Example API Call
The follow example API call invokes the validator defined above.
let query = "
mutation {
UserCreate(input: {
name: \"KENOBI\"
}) {
id
name
}
}
"
.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::validators::Validators;
use warpgrapher::engine::value::Value;
use warpgrapher::{Engine, Error};
static CONFIG: &str = "
version: 1
model:
User
- name: User
props:
- name: name
type: String
required: true
validator: NameValidator
";
#[derive(Clone, Debug)]
struct AppRequestContext {}
impl RequestContext for AppRequestContext {
type DBEndpointType = CypherEndpoint;
fn new() -> AppRequestContext {
AppRequestContext {}
}
}
fn name_validator(value: &Value) -> Result<(), Error> {
if let Value::Map(m) = value {
if let Some(Value::String(name)) = m.get("name") {
if name == "KENOBI" {
Err(Error::ValidationFailed {
message: format!(
"Input validator for {field_name} failed. Cannot be named KENOBI",
field_name = "name"
),
})
} else {
Ok(())
}
} else {
Err(Error::ValidationFailed {
message: format!(
"Input validator for {field_name} failed.",
field_name = "name"
),
})
}
} else {
Err(Error::ValidationFailed {
message: format!(
"Input validator for {field_name} failed.",
field_name = "name"
),
})
}
}
#[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");
// load validators
let mut validators: Validators = Validators::new();
validators.insert("NameValidator".to_string(), Box::new(name_validator));
// create warpgrapher engine
let engine: Engine<AppRequestContext> = Engine::new(config, db)
.with_validators(validators.clone())
.build()
.expect("Failed to build engine");
let query = "
mutation {
UserCreate(input: {
name: \"KENOBI\"
}) {
id
name
}
}
"
.to_string();
let metadata = HashMap::new();
let result = engine.execute(query, None, metadata).await.unwrap();
println!("result: {:#?}", result);
}