JSON Schema and OpenAPI
With the openapi feature every built-in schema implements the JsonSchema trait. The output is a serde_json::Value in JSON Schema 2020-12, which OpenAPI 3.1 uses directly.
vld = { version = "0.5", features = ["openapi"] }
use vld::prelude::*; // brings in JsonSchema with the feature on
let email = vld::string().min(2).max(50).email();
let js = email.json_schema();
assert_eq!(js["type"], "string");
assert_eq!(js["minLength"], 2);
assert_eq!(js["format"], "email");
The JsonSchema trait
pub trait JsonSchema {
fn json_schema(&self) -> serde_json::Value;
fn is_required(&self) -> bool { true } // false for optional / nullable / nullish / with_default
}
ZObject also has an inherent to_json_schema() that returns the same value.
Structs
schema! and #[derive(Validate)] structs get two functions:
vld::schema! {
#[derive(Debug)]
pub struct CreateUser {
pub name: String => vld::string().min(2).describe("Display name"),
pub email: String => vld::string().email(),
pub age: Option<u8> => vld::u8().optional(),
}
}
let schema = CreateUser::json_schema();
assert_eq!(schema["required"], serde_json::json!(["name", "email"]));
assert_eq!(schema["properties"]["name"]["description"], "Display name");
let doc = CreateUser::to_openapi_document();
assert_eq!(doc["openapi"], "3.1.0");
assert!(doc["components"]["schemas"]["CreateUser"].is_object());
Optional, nullable, nullish and defaulted fields are left out of required.
Free functions
| Function | Result |
|---|---|
vld::openapi::to_openapi_document(name, &schema) | a minimal OpenAPI 3.1 document with the schema under #/components/schemas/{name} |
vld::openapi::to_openapi_document_multi(&[(name, schema), …]) | the same for several schemas |
vld::openapi::nullable_schema(inner) | {"anyOf": [inner, {"type": "null"}]} |
The free functions do not add nested components; use the struct's to_openapi_document() for that.
Nested schemas
vld::nested(T::parse_value) embeds a nested type as {"type": "object"}. To reference it as a component, use vld::nested!(T):
vld::schema! {
#[derive(Debug, Clone)]
pub struct Address {
pub city: String => vld::string().min(1),
}
}
vld::schema! {
#[derive(Debug)]
pub struct Customer {
pub address: Address => vld::nested!(Address),
}
}
let doc = Customer::to_openapi_document();
assert_eq!(
doc["components"]["schemas"]["Customer"]["properties"]["address"]["$ref"],
"#/components/schemas/Address"
);
assert!(doc["components"]["schemas"]["Address"].is_object());
Nested components are collected transitively, so grandchildren are registered too. The same components are picked up by vld-utoipa, vld-schemars and vld-aide.
Mapping
| vld | JSON Schema |
|---|---|
string().min(n).max(n) | minLength / maxLength |
| string formats | format (email, uri, uuid, ipv4, date-time, …) |
regex(re) / starts_with / … | pattern |
number() / integers / f32() | type: number / type: integer + format: int32… / format: float |
min / gt / multiple_of | minimum / exclusiveMinimum / multipleOf |
array().min_len() / unique() / contains() | minItems / uniqueItems / contains + minContains |
| tuples | prefixItems |
record() / record_with_keys() | additionalProperties, propertyNames, minProperties / maxProperties |
literal(v) | const |
enumeration() / enum_of() | enum |
optional() / nullable() / nullish() | anyOf: [inner, {"type": "null"}] |
with_default(v) | default |
describe(text) | description |
| unions | anyOf (first match wins, so oneOf would be wrong) |
discriminated_union | anyOf of the variant schemas |
vld extensions
Some constraints have no JSON Schema keyword. vld emits them as x-* keys, which OpenAPI tools ignore and diff understands:
| Key | Emitted by |
|---|---|
x-past, x-future | date() / datetime() .past() / .future() |
x-minDecimal, x-maxDecimal, x-exclusiveMinDecimal, x-exclusiveMaxDecimal, x-multipleOfDecimal | decimal() bounds (as strings) |
x-minSeconds, x-maxSeconds | duration() bounds |
x-minFileSize, x-maxFileSize | file() size bounds |
x-minUniqueItems, x-maxUniqueItems | set() size bounds |
x-maxDepth | json_value().max_depth(n) |
Dates also use the standard-track formatMinimum / formatMaximum for min / max.