haematite-cli 0.21.9

The haematite command: serve a store, its door and Studio, answer MCP, query with jq, and host the store's assistant seat
Documentation
#![forbid(unsafe_code)]
use haematite_cli::serve::schema::{Collection as CollectionSchema, validate};
use serde_json::{Value, json};

type TestResult = Result<(), Box<dyn std::error::Error>>;

#[test]
fn schemaless_collections_accept_every_json_kind_without_transforming_it() -> TestResult {
    for bytes in [
        "null",
        "false",
        "123",
        "\"text\"",
        "[1, 2]",
        "{ \"extra\": true }",
    ] {
        let document: Value = serde_json::from_str(bytes)?;
        let before = document.clone();
        validate(None, &document)?;
        assert_eq!(document, before);
    }
    Ok(())
}

#[test]
fn required_core_allows_extensions_and_preserves_null_and_defaults() -> TestResult {
    let shape = CollectionSchema::compile(&json!({
        "type": "object", "required": ["name"],
        "properties": {"name": {"type": "string"}, "count": {"default": 4}}
    }))?;
    let document = json!({"name": "Buckley", "extra": {"value": null}});
    let before = document.clone();
    shape.validate(&document)?;
    assert_eq!(document, before);
    assert!(document.get("count").is_none());
    let Err(error) = shape.validate(&json!({"extra": true})) else {
        return Err("missing core field was accepted".into());
    };
    assert_eq!(error.path, "/name");
    Ok(())
}

#[test]
fn nested_array_refusal_names_the_field_with_escaped_json_pointer() -> TestResult {
    let shape = CollectionSchema::compile(&json!({
        "type": "array", "items": {"type": "object", "required": ["a/b~c"]}
    }))?;
    let Err(error) = shape.validate(&json!([{}])) else {
        return Err("missing nested field was accepted".into());
    };
    assert_eq!(error.path, "/0/a~1b~0c");
    Ok(())
}

#[test]
fn closed_shapes_require_an_explicit_constraint() -> TestResult {
    let shape = CollectionSchema::compile(&json!({
        "properties": {"name": {"type": ["string", "null"]}},
        "additionalProperties": false
    }))?;
    shape.validate(&json!({"name": null}))?;
    assert!(shape.validate(&json!({"extra": 1})).is_err());
    Ok(())
}

#[test]
fn numeric_constraints_follow_schema_equality_not_number_representation() -> TestResult {
    let shape = CollectionSchema::compile(&json!({"type": "integer", "enum": [1], "minimum": 1}))?;
    shape.validate(&json!(1.0))?;
    assert!(shape.validate(&json!(1.0001)).is_err());
    assert!(shape.validate(&json!(0)).is_err());
    Ok(())
}

#[test]
fn documents_do_not_lose_large_integers_to_floating_point_rounding() -> TestResult {
    let exact: Value = serde_json::from_str("18446744073709551616001")?;
    let different: Value = serde_json::from_str("18446744073709551616002")?;
    let shape = CollectionSchema::compile(&json!({"const": exact}))?;
    shape.validate(&exact)?;
    assert!(shape.validate(&different).is_err());
    let huge: Value = serde_json::from_str("1e400")?;
    validate(None, &huge)?;
    Ok(())
}

#[test]
fn declarations_refuse_unknown_constraints_malformed_shapes_and_external_refs() {
    for schema in [
        json!({"properties": {"name": {"pattern": "x"}}}),
        json!({"required": ["a", "a"]}),
        json!({"type": "invented"}),
        json!({"$ref": "file:///etc/passwd"}),
        json!({"$ref": "http://127.0.0.1:4000/"}),
        json!({"$schema": "https://example.invalid/schema"}),
    ] {
        assert!(
            CollectionSchema::compile(&schema).is_err(),
            "accepted {schema}"
        );
    }
}

#[test]
fn boolean_shapes_and_nesting_limits_are_enforced() -> TestResult {
    CollectionSchema::compile(&json!(true))?.validate(&json!({}))?;
    assert!(
        CollectionSchema::compile(&json!(false))?
            .validate(&json!({}))
            .is_err()
    );
    let mut schema = json!(true);
    for _ in 0..66 {
        schema = json!({"items": schema});
    }
    assert!(CollectionSchema::compile(&schema).is_err());
    Ok(())
}