Library
Compile a schema once; validate many documents. The full API surface is spec/api/ and docs.rs.
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 personAnd 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 matchedcount_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