Install

Add the crate

cargo add schematron
Version
0.16.0
MSRV
1.96 — policy: current stable minus two
Default features
serde, cli
Unsafe code
None
Licence
MIT, Apache-2.0, GPL-2.0-only, or GPL-3.0-only, at your option

Turning off the cli feature drops clap; turning off serde drops serde, serde_json, and Report::to_json. SVRL and text output are always available.

The shortest useful program

Validate a document

use schematron::{Document, Schema};

fn main() -> schematron::Result<()> {
    // Compiling is the expensive step: it resolves includes, expands
    // abstractions, and parses every XPath expression in the schema.
    let schema = Schema::from_path("rules.sch")?;
    let document = Document::from_path("data.xml")?;
    let report = schema.validate(&document)?;

    if report.is_valid() {
        println!("valid");
    } else {
        for failure in report.failures() {
            println!("{}: {}", failure.location, failure.text);
        }
    }
    Ok(())
}

Reports

A report is data, not text

One run renders three ways:

let svrl = report.to_svrl();   // SVRL, for other Schematron tooling
let json = report.to_json()?;  // JSON, keeping the tree structure
let text = report.to_text();   // for a person

And it can be queried directly, instead of scraped back out of text:

report.is_valid();                  // no assert failed
report.count_failures();            // how many did
report.with_flag("error").count();  // findings the schema flagged as errors
report.count_fired_rules();         // zero here means NO context matched

count_fired_rules() returning zero is the programmatic form of the "my schema does nothing" symptom: no rule context matched any node. Assert on it in your own tests and a broken schema fails loudly instead of passing silently.

Concurrency

Compile once, validate in parallel

Schema is immutable and Send + Sync:

use std::sync::Arc;

let schema = Arc::new(Schema::from_path("rules.sch")?);

for path in paths {
    let schema = Arc::clone(&schema);
    std::thread::spawn(move || {
        let document = Document::from_path(path)?;
        schema.validate(&document)
    });
}

Within one document, independent patterns can evaluate on separate threads. The report is identical either way:

let options = ValidateOptions::new().with_parallel_patterns(true);
let report = schema.validate_with(&document, &options)?;

Measure before turning it on — on a small document the threads cost more than they save. See spec/validation/.

Bounded memory

Streaming validation

For a document too large to comfortably hold fully in memory, validate_streaming validates one repeating record at a time — the document element's direct children, e.g. <order> under <orders> — parsed, matched, fired, and discarded before the next is parsed:

let schema = Schema::from_path("rules.sch")?;
schema.streaming_eligible()?; // checked once, before any document is read

let file = std::fs::File::open("orders.xml")?;
let report = schema.validate_streaming(file, &ValidateOptions::new())?;

Only for a schema whose active patterns are provably local to one record's own subtree: any <key>, key(), id(), document(), following::/preceding::/following-sibling::, or a schema-, phase-, or pattern-scoped <let>, refuses it by name rather than silently falling back to ordinary validation — a fallback that might exhaust the memory someone specifically asked to avoid using. See spec/streaming/.

Resolvers

Where includes come from

The default resolver reads local files and refuses http: and https:. Network access is something an application opts into by supplying its own Resolver — which is also how you serve includes from memory, from an embedded bundle, or from a database.

cargo run --example embedded_schema in the repo is a worked example of exactly that.

Runnable

Examples in the repo

cargo run --example validate_file        # the shortest useful program
cargo run --example report_formats       # SVRL, JSON, and text from one run
cargo run --example embedded_schema      # includes served from memory
cargo run --example parallel_validation  # one schema, eight threads
cargo run --example streaming_validation # bounded memory, one record at a time
cargo run --example xpath_engine         # the XPath engine on its own

API documentation on docs.rs The command line tool