requisite makes data-handling requirements part of Rust function signatures.
Values carry the state required by the next operation, and APIs state that
requirement in their parameter types.
use requisite::prelude::*;
fn db_lookup(id: Tainted<u64, Trusted>) -> String { /* ... */ }
let body = Tainted::<_, Untrusted>::from_input(request_body);
let customer_id = sanitize(body, clean_customer_id);
db_lookup(customer_id);The receiving function states its requirement in its parameter type. The compiler then verifies that callers perform the required transition.
| module | role |
|---|---|
trust |
routes external input through a named sanitizer before trusted use |
confidence |
converts a probability into an action tier and issues proof for the highest tier |
fresh |
checks TTLs at read time and binds scoped values to their use site |
cargo add requisiterequisite supports Rust 1.70 and later.
| guide | use it to |
|---|---|
| Getting started | add requisite and build an end-to-end flow |
| Trust transitions | route external input through infallible or fallible policies |
| Confidence gates | validate probabilities and authorize high-confidence actions |
| Freshness | enforce TTLs, recover stale values, and scope live data |
| Integration patterns | place wrappers at application boundaries and design typed APIs |
use requisite::prelude::*;
fn db_lookup(id: Tainted<u64, Trusted>) -> String { /* ... */ }
let raw = Tainted::<_, Untrusted>::from_input("42; DROP TABLE customers".into());
println!("{:?}", raw.as_ref());
let clean = try_sanitize(raw, |s| {
s.chars()
.filter(char::is_ascii_digit)
.collect::<String>()
.parse::<u64>()
})?;
db_lookup(clean);Tainted<T, Untrusted> marks data at an input boundary. sanitize handles
infallible transformations, while try_sanitize returns validation errors to
the caller. Successful transitions produce Tainted<U, Trusted>, which trusted
sinks accept directly.
The sanitizer closure defines what “trusted” means for a specific sink. Keep
the destination's cleaning policy in that closure. widen() supports APIs that
accept a lower trust requirement.
See Trust transitions for validation, type-changing policies, and sink design.
Confident<T> carries a value and a validated probability. gate() applies the
default thresholds, and gate_with() accepts application-defined thresholds.
let aurora = Confident::new(forecast(kp_index), 0.97)?;
let plan = match aurora.gate() {
Gate::HighConfidence(proof, _) => {
wake_slepp(proof);
"woke him up"
}
Gate::Likely(_) => {
buzz_phone();
"sent a buzz"
}
Gate::Unsure(_) => "logged it",
};The highest tier includes a Certain token. Functions such as wake_slepp can
require that token, making a successful confidence check part of their call
contract. Lower tiers remain available for proportionate actions such as a
notification or log entry.
let forecast = Confident::new(predicted_event, 0.99)?;
let thresholds = Thresholds::new(0.70, 0.98)?;
match forecast.gate_with(thresholds) {
Gate::HighConfidence(proof, value) => act(proof, value),
Gate::Likely(value) => notify(value),
Gate::Unsure(value) => record(value),
}Custom thresholds may raise the Certain boundary above 0.95. The lower
bound keeps every Certain token consistent for functions that require one.
See Confidence gates for threshold configuration and typed action authorization.
let quote = Fresh::fetch(price, Duration::from_secs(30));
match quote.get() {
Ok(price) => charge(customer, *price),
Err(stale) => println!("price is {} old, refetching", stale.age.as_secs()),
}Fresh::get compares the value's age with its TTL and returns either the value
or a Stale error containing both durations. into_inner performs the same
check while consuming the wrapper, allowing ownership of a fresh non-Clone
value to move into the next operation. A stale result returns the value in
StaleValue<T>. fetched_at supports deterministic tests and rejects
timestamps ahead of the current monotonic clock.
with_live handles values whose validity is tied to one operation:
with_live(price, |live| {
let total = live.get().cents + tax;
total
});The branded Live value stays within the closure while derived owned values can
be returned. The closure’s return type is independent of the private lifetime
brand, so retaining Live produces a lifetime error at the return site.
See Freshness for cache access, stale-value recovery, and deterministic tests.
| requirement | enforcement |
|---|---|
| trusted sink arguments | Tainted<T, Trusted> parameter types |
| confidence-based action tiers | Confident::gate(), gate_with(), and Gate<T> |
| high-confidence action authorization | private Certain construction |
| operation-scoped values | branded Live<'id, T> lifetimes |
| TTL validity | Fresh::get() or into_inner() at read time |
| sanitization policy | the closure passed to sanitize or try_sanitize |
Runtime checks produce types that carry their result into the next operation.
For example, gate() converts a probability into a Gate<T>, and the highest
tier carries the Certain token required by sensitive actions.
tests/ui/ contains compile-fail cases for each type-level contract. trybuild
compares the compiler output with checked-in snapshots, so CI detects changes
that weaken a contract.
| test | contract |
|---|---|
untrusted_to_sink.rs |
trusted sinks require sanitized input |
widen_upward.rs |
trust promotion goes through sanitize |
confidence_as_bool.rs |
confidence values go through gate |
forge_certain.rs |
Certain originates from the highest gate tier |
escape_live.rs |
scoped values remain inside their closure |
Regenerate snapshots after an intentional compiler diagnostic change:
TRYBUILD=overwrite cargo test --test compile_failReview the resulting diff to confirm that each failure still exercises its documented contract.
cargo run --example payment_flow
cargo testsrc/trust.rs Tainted<T, Tr>, sanitize, widen
src/confidence.rs Confident<T>, gate() -> Gate, Certain
src/fresh.rs Fresh<T> with a TTL; scoped::with_live
examples/payment_flow.rs payment and alerting example
tests/runtime.rs behavior tests
tests/ui/*.rs + .stderr compile-fail tests
docs/ user guides
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT license (LICENSE-MIT)
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.