A policy can be checked against ODRL profiles. The result is a Report with a verdict and findings. OdrlProse can show it under the sentences, OdrlProseView can mark it at the slots it concerns, and ProseProblems lists it with a way to jump to each finding and apply each fix.
A verdict of Incomplete is never a pass. It means no error was found but something could not be checked, such as a declared profile that is not loaded.
A Wording holds the loaded profiles. A Validator pairs a wording with Options.
Wording::new().with_profile(ProfileVocab::parse(text)?) loads a profile from JSON-LD text. Turtle is read when prose-core is built with its turtle feature.
Options::advisory() is the default. Profiles widen and annotate the reading, and nothing is an error.
Options::strict() reads the vocabulary as closed: ODRL plus the loaded profiles. The structural rules of the ODRL Information Model stay off until Options::structural(true).
A left operand a profile labels with a plural noun phrase says so with prose:plural true, in JSON-LD or Turtle. Its operators then agree in the plural ("the time windows are earlier than"), in the reader, the editor sentence and the operator select. ODRL's own terms keep their built-in number.
Validator::new(Arc<Wording>, Options), then validate(&EditDoc) -> Report or validate_json(&str) -> Report.
Report has mode, verdict, findings and gaps, and is #[non_exhaustive]. Verdict is Conforms, Violations or Incomplete, also #[non_exhaustive]. Always match with a wildcard arm.
OdrlProse writes a section.prose-violations after the policies when it has a validator and violations is true. With a validator and violations left false, nothing changes in the output, and with violations and no validator, there is nothing to show.
#[function_component(ReaderWithViolations)]
fn reader_with_violations() -> Html {
let wording = Rc::new(Wording::new());
let validator = Validator::new(Arc::new((*wording).clone()), Options::strict());
html! {
<OdrlProse json={POLICY} wording={Some(SharedWording(wording))}
validator={Some(validator)} violations={true} />
}
}
From prose-yew/tests/site_examples.rs, region reader_violations.
The panel below is the output the repository pins for a policy that breaks a closed research profile: prose-yew/tests/snapshots/strict_read_violations.html, produced by ssr_strict.rs. It is styled by the example stylesheet.
Agreement urn:policy:violating
An agreement between hospital (assigner) and university (assignee).
Written to the profile Research Data Profile (https://example.org/odrl/profile/research, version 0.1.0).
university may fine-tune cohort-2026, as permitted by the assigner hospital.
Limits on the action:
The action "fine-tune" is limited to cases where:
the number of data subjects is at least "ten"
Applies only if:
the research purpose is at most academic research
university may distil cohort-2026, as permitted by the assigner hospital.
university may bar cohort-2026, as permitted by the assigner hospital.
Terms from the profile
Fine-tuneex:fineTune
To adapt an already trained model using the Asset.
Usage: Fine-tuning implies that the Asset may be read.
A kind of: Train
Implies: read
From Research Data Profile
Number of data subjectsex:dataSubjectCount
The number of distinct data subjects represented in a result derived from the Asset. Right operand value MUST be an xsd:integer.
Usage: Only the gteq and gt operators SHOULD be used. Small counts risk re-identification.
From Research Data Profile
Research purposeex:researchPurpose
The research purpose the action of the Rule is exercised for.
A kind of: purpose
From Research Data Profile
Academic researchex:academic
Research carried out by an accredited academic institution with no commercial sponsor.
From Research Data Profile
Does not conform
prose.datatype-lexical "ten" is not an integer, which the profile expects for "ex:dataSubjectCount"
prose.operator-not-allowed the profile allows only the operators eq, isAnyOf or isNoneOf for "ex:researchPurpose"
profile.undeclared-in-namespace the action "ex:distil" is not declared by the loaded profile <https://example.org/odrl/profile/research>
profile.unknown-term the action "foo:bar" is not defined by ODRL or by a loaded profile
The status line exists only for a strict report. Its class and words come from the verdict:
Verdict
Class
Words (Labels field)
Verdict::Conforms
prose-conforms
status_conforms
Verdict::Incomplete
prose-incomplete
status_incomplete
any other verdict
prose-violations-found
status_violations
A verdict this version does not know is shown as a failure, not as a pass.
EditConfig::with_wording loads the profiles into the editor, and with_strict(Options::strict()) validates against them and makes the add buttons create admissible values. use_prose_editor_with takes the validator and a StrictEdit policy, and use_validation gives the report.
#[function_component(Strict)]
fn strict() -> Html {
let cfg = Rc::new(
EditConfig::default()
.with_wording(Rc::new(Wording::new()))
.with_strict(Options::strict()),
);
let with = use_prose_editor_with(
|| read_model(POLICY).unwrap_or_default(),
Rc::new(cfg.rules.clone()),
cfg.validator.clone(),
StrictEdit::Flag,
);
let report = use_validation(&with.editor.doc, cfg.validator.as_ref());
html! {
<>
<OdrlProseView doc={with.editor.doc.clone()} mode={EditMode::Edit} config={cfg}
onedit={with.editor.onedit.clone()}
refusal={with.refused.as_ref().and_then(|(ev, err)| err.issue(ev))} />
<ProseProblems report={report} onfix={with.apply_all.clone()} />
</>
}
}
From prose-yew/tests/site_examples.rs, region strict.
Pass the hook Rc::new(cfg.rules.clone()) and cfg.validator.clone() as the example does. The reducer applies the rules it is handed, not the config's.
StrictEdit::Flag, the default, commits every edit the rules allow and lets the validator mark a violation. StrictEdit::RefuseTerms makes the reducer refuse a term outside the profile written into a vocabulary slot, as EditError::OutsideProfile. Show the refusal with refusal={with.refused.as_ref().and_then(|(ev, err)| err.issue(ev))}.
Under a closed profile, a vocabulary slot is a closed combobox and takes only what the validator admits. Enter on text that names nothing admissible keeps the draft in the slot, with the reason and the nearest term. Escape reverts it. When focus leaves the slot, the draft is given up: the slot goes back to its value and a polite live region says so. So text typed into a closed combobox can be lost, with StrictEdit::Flag too. A closed slot has data-closed="true". The operator select groups its options into those the profile allows and those it does not.
ProseProblems groups the findings by rule, in document order. Each finding has a "Go to" button and one button for each fix. A fix is a list of events: give onfix the apply_all of the hook, which applies them as one step with one undo entry. The events carry expect ids, so a fix from a report the document has moved on from is refused as stale.
onjump receives the IssueTarget of the finding. The host moves focus to the [data-slot] or [data-node] element of the view that holds that path. After a fix removes the focused button, focus moves to the panel heading if it fell to the page body.
The classes of the panel are on the CSS hooks page. The demo, demo/src/edit.rs, shows a complete host with issues, onjump and an undo button.