Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Use the Rust library

Everything the esdieff command does is available as a Rust library, so you can embed model checking, inspection, generation and revision comparison in your own tools. The library uses no unsafe code and performs no network access.

Add the crate

There is no release on crates.io yet, so depend on the repository directly:

[dependencies]
esdieff = { git = "https://github.com/pederbe/esdieff" }

The crate builds on stable Rust with the 2024 edition. Pin a commit with rev = "..." once you depend on it in CI.

The entry points

A Catalogue holds the documents you supply. Build one from SourceInput values, each with a key that names it in reports: SourceInput::file reads a path, SourceInput::supplied takes bytes you already hold, and .sample() marks a sample value rather than a model. Every operation is a method on the catalogue that takes a request and returns a result you can query or render:

OperationRequestResult
Check a modelCheckRequestReport
Inspect one definitionInspectRequestReport
Read the composed interfaceEffectiveRequestEffectiveModel
Check one sampleSampleRequestReport
Generate valuesGenerationRequestGenerationResult
Check a recorded traceTraceRequestTraceResult
Compose a thingAuthorRequestAuthorResult
Edit qualitiesEditRequestEditResult
Compare revisionsComparisonRequestRevisionComparison

Results borrow the catalogue, so keep it alive while you read them. Each has to_json() and to_human() for the same reports the command prints, and a disposition whose exit_code() matches the command’s exit status.

A complete example

This program checks the thermostat model from Install and try a model with the full assessment, reads the effective range of its target temperature and prints the property as written in the file. Create a new binary crate, add the dependency above, put thermostat.sdf.json in the crate directory and use this src/main.rs:

use esdieff::{Assessment, Catalogue, CheckRequest, Class, EffectiveRequest, InspectRequest, Lookup, SourceInput};
use std::path::Path;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Supply the model files. Every input is a candidate, and its key names it in reports.
    let catalogue = Catalogue::from_inputs(&[SourceInput::file(
        "thermostat",
        Path::new("thermostat.sdf.json"),
    )])?;
    let thermostat = catalogue.source_id("thermostat").expect("supplied source");

    // Check the thermostat against every RFC 9880 model requirement Esdieff records.
    let request = CheckRequest::new()
        .root(thermostat)
        .assessment(Assessment::FullBaseline);
    let report = catalogue.check(request)?;
    println!("check: {:?}", report.outcome().disposition());
    for condition in report.diagnostics().conditions() {
        println!("{}: {}", condition.code(), condition.message());
    }

    // Read the effective target temperature range, with inherited qualities applied.
    let effective = catalogue.effective(EffectiveRequest::new(thermostat))?;
    let Lookup::Found(object) = effective.definition(Class::Object, "Thermostat") else {
        panic!("Thermostat missing")
    };
    let (_, target) = object
        .children(Class::Property)
        .require()?
        .find(|(name, _)| *name == "targetTemperature")
        .expect("targetTemperature");
    let description = target.require()?.data_description().require()?;
    println!(
        "targetTemperature: {} to {}",
        description.minimum().require()?.lexeme(),
        description.maximum().require()?.lexeme()
    );

    // Show the authored source of that property as written in the file.
    let pointer = "/sdfObject/Thermostat/sdfProperty/targetTemperature";
    let inspection = catalogue.inspect(InspectRequest::new(thermostat, pointer))?;
    if let Some(json) = inspection.inspection_json() {
        println!("source: {}", json.raw_json());
    }
    std::process::exit(report.outcome().disposition().exit_code().into());
}
$ cargo run --quiet
check: Passed
targetTemperature: 5 to 30
source: {
          "description": "Requested room temperature in half-degree increments",
          "type": "number", "nullable": false, "minimum": 5, "maximum": 30,
          "multipleOf": 0.5, "unit": "Cel", "readable": true, "writable": true
        }

A few things to notice. Operations return Err(&'static str) only for an invalid request, such as an unknown source key. A model problem is never an error: it is a finding in the report, and the disposition says whether the check passed. Fields that may be absent, unfinished or outside the supported meaning are Field values, and require() turns anything but a present value into an error, which suits a tool that expects a complete model. Numbers are exact: lexeme() returns the text from the document, and ExactNumber compares values without converting them to floating point. The source view prints the member byte for byte, whitespace included.

Keep the memory and work bounded

Every operation runs within Limits, which cap input size, decoding depth, work and report size, so an untrusted document cannot make a tool run away. The defaults suit ordinary models. Catalogue::take_inputs_with_limits sets others, and an operation that reaches a limit reports the work it could not finish instead of failing silently. ADR 0009 explains the approach.

Where the contracts are

Each operation’s reference page states what its result promises, with a Rust example at the top:

The repository’s examples directory holds complete programs that read effective values and requirement coverage.