Skip to content

Latest commit

 

History

History
246 lines (185 loc) · 6.76 KB

File metadata and controls

246 lines (185 loc) · 6.76 KB

schema-value — Rust Reference

JSON Schema Draft-07 as a Rust object. Structure maps to accessors, logic maps to methods.

# Cargo.toml
schema-value = { path = "schema-value" }   # local workspace

Core Idea

A JS object has state and behavior together. JSON serialization stripped the behavior. schema-value restores it: the schema defines the class, the data is the instance.

use schema_value::SchemaValue;
use serde_json::json;

let schema = json!({
    "type": "object",
    "properties": {
        "email": {"type": "string"},
        "age":   {"type": "integer", "minimum": 0},
        "roles": {"type": "array", "items": {"enum": ["admin", "user", "guest"]}}
    },
    "required": ["email", "roles"]
});

let data = json!({"email": "alice@example.com", "age": 30, "roles": ["admin"]});
let user = SchemaValue::new(data, schema);

// Index access (structure → data)
assert_eq!(user["email"], json!("alice@example.com"));
assert_eq!(user["age"],   json!(30));

// get() propagates sub-schema
let email_sv = user.get("email").unwrap();  // SchemaValue with email sub-schema

Draft-07 Logic → Methods

Each Draft-07 logical operator maps to a method on SchemaValue:

Draft-07 keyword Method Semantics
oneOf one_of() XOR — exactly one branch matches
anyOf any_of() OR — all matching branches
allOf all_of() AND — merge all sub-schemas
not not_of(schema) NOT — data must not match
if/then/else if_then() CASE WHEN — conditional branch
properties project() SELECT — keep only schema-defined fields
contains contains(schema) EXISTS — array element check

Usage Patterns

oneOf — dispatch by type

let schema = json!({
    "oneOf": [
        {"properties": {"type": {"const": "text"},  "content": {"type": "string"}}},
        {"properties": {"type": {"const": "image"}, "url": {"type": "string"}}},
    ]
});

let event = SchemaValue::new(
    json!({"type": "text", "content": "hello"}),
    schema
);

let branch = event.one_of()?;    // SchemaValue bound to matching sub-schema
assert_eq!(branch["content"], json!("hello"));

if_then — conditional logic

let schema = json!({
    "if":   {"properties": {"role": {"const": "admin"}}},
    "then": {"properties": {"level": {"minimum": 5}}},
    "else": {"properties": {"level": {"maximum": 4}}}
});

let user = SchemaValue::new(json!({"role": "admin", "level": 10}), schema);
let branch = user.if_then();     // bound to "then" branch

project — strip unknown fields

let schema = json!({
    "properties": {
        "name": {"type": "string"},
        "age":  {"type": "integer"}
    }
});

let raw = SchemaValue::new(
    json!({"name": "Alice", "age": 30, "extra": "noise"}),
    schema
);
let clean = raw.project()?;      // {"name": "Alice", "age": 30}

x-methods — evaluate method specs from schema

When your schema defines x-methods, extract the method body and validate against it:

let schema = json!({
    "properties": {"age": {"type": "integer", "minimum": 0}},
    "x-methods": {
        "is_adult": {
            "properties": {"age": {"minimum": 18}}
        }
    }
});

let user = SchemaValue::new(json!({"age": 20}), schema.clone());

// Extract x-methods body, validate as sub-schema
let method_schema = schema["x-methods"]["is_adult"].clone();
let check = SchemaValue::new(user.as_value().clone(), method_schema);
check.validate()?;   // Ok — age >= 18

Validation

use schema_value::SchemaValue;
use serde_json::json;

let schema = json!({
    "properties": {
        "email": {"type": "string"},
        "age":   {"type": "integer", "minimum": 0}
    },
    "required": ["email"]
});

// Valid
let user = SchemaValue::new(json!({"email": "alice@example.com", "age": 30}), schema.clone());
assert!(user.validate().is_ok());

// Invalid — returns Err with path + reason
let bad = SchemaValue::new(json!({"age": -1}), schema);
let errs = bad.validate().unwrap_err();
// errs[0]: path="", message="missing required field 'email'"

Access: [] vs get()

// sv["key"] → &Value   (fast, no schema propagation)
let raw: &Value = &sv["name"];

// sv.get("key") → Option<SchemaValue>   (propagates sub-schema)
let name_sv: Option<SchemaValue> = sv.get("name");

Use get() when you need the sub-schema context (e.g., before calling composition methods on a nested object). Use [] for simple data access.


Schema as Object Class

SchemaValue treats the schema as a class definition:

let schema = json!({
    "properties": {
        "name":  {"type": "string", "minLength": 1},
        "score": {"type": "number", "minimum": 0, "maximum": 100}
    },
    "required": ["name"],
    "x-methods": {
        "passed": {"properties": {"score": {"minimum": 60}}}
    }
});

// Instance
let student = SchemaValue::new(json!({"name": "Alice", "score": 85}), schema.clone());

// Access state
assert_eq!(student["name"],  json!("Alice"));
assert_eq!(student["score"], json!(85));

// Evaluate method spec
let method_schema = schema["x-methods"]["passed"].clone();
let check = SchemaValue::new(student.as_value().clone(), method_schema);
assert!(check.validate().is_ok());   // score >= 60

API Summary

SchemaValue::new(data, schema)         // construct with schema
SchemaValue::without_schema(data)      // construct without schema

sv["key"]                              // &Value — fast field access
sv.get("key")                          // Option<SchemaValue> — with sub-schema
sv.set("key", value)?                  // validated mutation
sv.as_value()                          // &Value — inner data
sv.into_inner()                        // Value — consume, drop schema
sv.validate()                          // Result<(), Vec<ValidationError>>

// Draft-07 logic methods
sv.one_of()                            // Result<SchemaValue>  (XOR)
sv.any_of()                            // Result<Vec<SchemaValue>> (OR)
sv.all_of()                            // SchemaValue  (AND merged)
sv.not_of(Option<&Value>)              // bool
sv.if_then()                           // SchemaValue  (conditional branch)
sv.project()                           // Result<SchemaValue> (SELECT)
sv.contains(Option<&Value>)            // bool

Draft-07 Conformance

Official JSON Schema Test Suite: 98.4% (666/677 cases).

Known gaps:

  • Float/int equivalence in const/enum — serde_json limitation (8 cases)
  • $ref resolution — not implemented (3 cases)

References

  • Source: projects/socks5-mesh-rust/schema-value/
  • Philosophy: methodology/sdd-philosophy.md
  • x- extensions: methodology/x-extensions.md
  • Python equivalent: references/python/schema2object.md