Skip to main content
Schema validation ensures your documents conform to a defined structure, preventing invalid data from entering your database.

Why use schema validation?

Data integrity

Prevent invalid documents from being inserted or updated

Type safety

Enforce field types, required fields, and value constraints

Documentation

Schema serves as documentation for your data model

Validation errors

Get clear error messages when validation fails

Basic schema

Creating a schema

Schemas are defined using a JSON Schema-like structure.
use jasonisnthappy::{Database, Schema, ValueType};
use std::collections::HashMap;

let db = Database::open("my.db")?;

// Create a simple schema
let mut schema = Schema::new();
schema.value_type = Some(ValueType::Object);
schema.required = Some(vec!["name".to_string(), "email".to_string()]);

// Set the schema on the collection
db.set_schema("users", schema)?;

println!("Schema applied to users collection");

Validation in action

let users = db.collection("users");

// ✅ Valid: has required fields
users.insert(json!({
    "name": "Alice",
    "email": "alice@example.com"
}))?;

// ❌ Invalid: missing required field "email"
let result = users.insert(json!({
    "name": "Bob"
}));
assert!(result.is_err());

Type validation

Supported types

Jasonisnthappy supports these JSON types:
  • ValueType::String - String values
  • ValueType::Number - Numeric values (integers or floats)
  • ValueType::Integer - Integer values only
  • ValueType::Boolean - true/false
  • ValueType::Object - Nested objects
  • ValueType::Array - Arrays
  • ValueType::Null - Null values

Type enforcement

let mut schema = Schema::new();
schema.value_type = Some(ValueType::Object);

let mut properties = HashMap::new();

// String field
let mut name_schema = Schema::new();
name_schema.value_type = Some(ValueType::String);
properties.insert("name".to_string(), name_schema);

// Integer field
let mut age_schema = Schema::new();
age_schema.value_type = Some(ValueType::Integer);
properties.insert("age".to_string(), age_schema);

// Boolean field
let mut active_schema = Schema::new();
active_schema.value_type = Some(ValueType::Boolean);
properties.insert("active".to_string(), active_schema);

schema.properties = Some(properties);
db.set_schema("users", schema)?;
users.insert(json!({
    "name": "Alice",
    "age": 30,
    "active": true
}))?;  // ✅ OK

Field constraints

String constraints

Validate string length and content.
let mut email_schema = Schema::new();
email_schema.value_type = Some(ValueType::String);
email_schema.min_length = Some(5);   // At least 5 characters
email_schema.max_length = Some(100); // At most 100 characters

properties.insert("email".to_string(), email_schema);
// ✅ Valid: within length bounds
users.insert(json!({"email": "alice@example.com"}))?;

// ❌ Invalid: too short
users.insert(json!({"email": "a@b"}))?;  // Error!

// ❌ Invalid: too long
users.insert(json!({"email": "a".repeat(101)}))?;  // Error!

Number constraints

Validate numeric ranges.
let mut age_schema = Schema::new();
age_schema.value_type = Some(ValueType::Integer);
age_schema.minimum = Some(0.0);    // Non-negative
age_schema.maximum = Some(150.0);  // Reasonable maximum

properties.insert("age".to_string(), age_schema);
// ✅ Valid: within range
users.insert(json!({"age": 30}))?;

// ❌ Invalid: below minimum
users.insert(json!({"age": -5}))?;  // Error!

// ❌ Invalid: above maximum
users.insert(json!({"age": 200}))?;  // Error!

Array constraints

Validate array length and item types.
let mut tags_schema = Schema::new();
tags_schema.value_type = Some(ValueType::Array);
tags_schema.min_length = Some(1);   // At least one tag
tags_schema.max_length = Some(10);  // Maximum 10 tags

// Validate array items
let mut item_schema = Schema::new();
item_schema.value_type = Some(ValueType::String);
tags_schema.items = Some(Box::new(item_schema));

properties.insert("tags".to_string(), tags_schema);
// ✅ Valid: array of strings
users.insert(json!({
    "tags": ["developer", "rust", "database"]
}))?;

// ❌ Invalid: empty array
users.insert(json!({"tags": []}))?;  // Error!

// ❌ Invalid: wrong item type
users.insert(json!({"tags": ["valid", 123]}))?;  // Error!

Enum validation

Restrict fields to specific allowed values.
let mut status_schema = Schema::new();
status_schema.enum_values = Some(vec![
    json!("pending"),
    json!("active"),
    json!("inactive"),
    json!("banned"),
]);

properties.insert("status".to_string(), status_schema);
// ✅ Valid: one of the allowed values
users.insert(json!({"status": "active"}))?;

// ❌ Invalid: not in enum
users.insert(json!({"status": "unknown"}))?;  // Error!
Enum validation works with any JSON value type, not just strings:
let mut priority_schema = Schema::new();
priority_schema.enum_values = Some(vec![json!(1), json!(2), json!(3)]);

Nested objects

Validate structure of nested objects.
let mut schema = Schema::new();
schema.value_type = Some(ValueType::Object);

let mut properties = HashMap::new();

// Define address schema (nested object)
let mut address_schema = Schema::new();
address_schema.value_type = Some(ValueType::Object);
address_schema.required = Some(vec!["city".to_string(), "country".to_string()]);

let mut address_props = HashMap::new();

let mut city_schema = Schema::new();
city_schema.value_type = Some(ValueType::String);
address_props.insert("city".to_string(), city_schema);

let mut country_schema = Schema::new();
country_schema.value_type = Some(ValueType::String);
address_props.insert("country".to_string(), country_schema);

address_schema.properties = Some(address_props);
properties.insert("address".to_string(), address_schema);

schema.properties = Some(properties);
db.set_schema("users", schema)?;
users.insert(json!({
    "name": "Alice",
    "address": {
        "city": "New York",
        "country": "USA",
        "zip": "10001"  // Optional field, OK
    }
}))?;  // ✅ OK

Complete example

Putting it all together: a complete user schema.
use jasonisnthappy::{Database, Schema, ValueType};
use std::collections::HashMap;
use serde_json::json;

let db = Database::open("my.db")?;

// Root schema
let mut schema = Schema::new();
schema.value_type = Some(ValueType::Object);
schema.required = Some(vec![
    "name".to_string(),
    "email".to_string(),
    "age".to_string(),
]);

let mut properties = HashMap::new();

// Name: string, 1-100 characters
let mut name_schema = Schema::new();
name_schema.value_type = Some(ValueType::String);
name_schema.min_length = Some(1);
name_schema.max_length = Some(100);
properties.insert("name".to_string(), name_schema);

// Email: string, 5-255 characters
let mut email_schema = Schema::new();
email_schema.value_type = Some(ValueType::String);
email_schema.min_length = Some(5);
email_schema.max_length = Some(255);
properties.insert("email".to_string(), email_schema);

// Age: integer, 0-150
let mut age_schema = Schema::new();
age_schema.value_type = Some(ValueType::Integer);
age_schema.minimum = Some(0.0);
age_schema.maximum = Some(150.0);
properties.insert("age".to_string(), age_schema);

// Status: enum (optional)
let mut status_schema = Schema::new();
status_schema.enum_values = Some(vec![
    json!("active"),
    json!("inactive"),
    json!("pending"),
]);
properties.insert("status".to_string(), status_schema);

// Tags: array of strings (optional)
let mut tags_schema = Schema::new();
tags_schema.value_type = Some(ValueType::Array);
tags_schema.max_length = Some(10);
let mut tag_item = Schema::new();
tag_item.value_type = Some(ValueType::String);
tags_schema.items = Some(Box::new(tag_item));
properties.insert("tags".to_string(), tags_schema);

schema.properties = Some(properties);

// Apply schema
db.set_schema("users", schema)?;

// Test validation
let users = db.collection("users");

// ✅ Valid document
users.insert(json!({
    "name": "Alice",
    "email": "alice@example.com",
    "age": 30,
    "status": "active",
    "tags": ["developer", "rust"]
}))?;

println!("User inserted successfully!");

Managing schemas

Get current schema

if let Some(schema) = db.get_schema("users") {
    println!("Schema: {:?}", schema);
} else {
    println!("No schema set");
}

Update schema

Replace the existing schema with a new one.
let mut updated_schema = Schema::new();
updated_schema.value_type = Some(ValueType::Object);
// ... define fields ...

db.set_schema("users", updated_schema)?;
Updating a schema does not validate existing documents. Only new inserts and updates are validated.

Remove schema

Disable validation on a collection.
db.remove_schema("users")?;
println!("Schema removed - validation disabled");

Schema validation with updates

Schemas are validated on both inserts and updates.
// Insert valid document
let id = users.insert(json!({
    "name": "Alice",
    "email": "alice@example.com",
    "age": 30
}))?;

// ✅ Valid update: age is still an integer
users.update_by_id(&id, json!({"age": 31}))?;

// ❌ Invalid update: age must be integer
let result = users.update_by_id(&id, json!({"age": "thirty-one"}));
assert!(result.is_err());

Error messages

Validation errors provide clear messages about what failed.
let result = users.insert(json!({
    "name": "Bob",
    "age": 200  // Exceeds maximum
}));

if let Err(e) = result {
    println!("Validation error: {}", e);
    // Output: "Schema validation error: Number at 'age' (200) exceeds maximum 150"
}

Best practices

Start with required fields and types:
let mut schema = Schema::new();
schema.value_type = Some(ValueType::Object);
schema.required = Some(vec!["id".to_string(), "name".to_string()]);
Add constraints (min/max, enum, etc.) as your requirements evolve.
Use enums for fixed sets of values:
// Better than free-form strings
status_schema.enum_values = Some(vec![
    json!("draft"),
    json!("published"),
    json!("archived"),
]);
Don’t over-constrain:Excessively strict schemas make it hard to evolve your data model. Leave room for optional fields and future extensions.

Common patterns

Email field

let mut email_schema = Schema::new();
email_schema.value_type = Some(ValueType::String);
email_schema.min_length = Some(5);
email_schema.max_length = Some(255);
// Note: For true email validation, validate in application code

Timestamp field

let mut created_schema = Schema::new();
created_schema.value_type = Some(ValueType::Integer);
created_schema.minimum = Some(0.0);  // Unix timestamp (non-negative)

Price field

let mut price_schema = Schema::new();
price_schema.value_type = Some(ValueType::Number);
price_schema.minimum = Some(0.0);  // Non-negative

Polymorphic data

For documents that can have different structures, omit strict validation:
// Minimal schema - allows flexibility
let mut schema = Schema::new();
schema.value_type = Some(ValueType::Object);
schema.required = Some(vec!["type".to_string()]);

// Validate "type" field in application code

Next steps

CRUD operations

Insert and update with validation

Indexes

Create unique indexes for constraints

Aggregation

Analyze validated data

Change streams

Watch for validation errors

Build docs developers (and LLMs) love