- resolve or unlink 25 preexisting rustdoc warnings: broken intra-doc links (error variants), public-docs-linking-private-items (crate-internal cursor/registry names), and one stale reference to a `Store::window_host` that never existed (the form branch is `sweep_window`'s engine-enum match) - `clamped` runs its arithmetic in u64 and narrows to usize after clamping, so a wide u64 offset can never wrap onto a small slice position on narrow-pointer targets; the cursor contract tests pin the overflowing-offset and overflowing-length forms - zero rustdoc warnings under RUSTDOCFLAGS=-D warnings Verification: cargo test (69+19+3 green), cargo test --all-features (124+54+4 green), cargo clippy --all-targets -- -D warnings green, cargo fmt --check green, RUSTDOCFLAGS=-D warnings cargo doc --no-deps green.
134 lines
5.6 KiB
Rust
134 lines
5.6 KiB
Rust
//! The error family (store-api.md §Error model; ADR-012 §6.5, §1).
|
|
//!
|
|
//! **One enum, both altitudes.** This is a *single* enum serving both
|
|
//! altitudes of the crate's surface. The [`Backend`][crate::backend]
|
|
//! trait's methods return this family but its *effective* members are
|
|
//! `Error::Missing`, `Error::Verification`, `Error::Io`, and
|
|
//! `Error::KeyInvalid` — at the
|
|
//! trait boundary, keys arrive opaque (so `Error::KeyInvalid` never fires
|
|
//! there) and GC delete-arbitration answers flow up as data, not as
|
|
//! errors (so `Error::GcRefuse` never fires below the facade). The facade's
|
|
//! public surface re-exposes this same family with `Error::GcRefuse` added:
|
|
//! the facade decides and owns the refusal. The altitude discipline is
|
|
//! carried by doc comments and construction discipline, not by two
|
|
//! separate types (named design point audited at the review gate;
|
|
//! store-api.md §Error model).
|
|
//!
|
|
//! The variant set is ADR-pinned (ADR-012 §6.5's family statement);
|
|
//! new failure families are new ADRs, so `#[non_exhaustive]` is
|
|
//! deliberately not applied.
|
|
|
|
/// The store error family — one enum with members at two altitudes
|
|
/// (store-api.md §Error model; ADR-012 §6.5, §1).
|
|
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
|
|
pub enum Error {
|
|
/// Key absent, on ops that signal absence via `Err`:
|
|
/// `read_range` and `delete` (ADR-012 §6.5 ruling 5;
|
|
/// store-api.md §Error model).
|
|
///
|
|
/// A miss on `get` and `stat` is a value, not an error — they
|
|
/// signal absence with `None` and never carry `Missing`. The
|
|
/// facade's read paths enforce this behaviorally: both-tier misses
|
|
/// resolve `Ok(None)` on get/stat (see `src/store.rs`'s read-path
|
|
/// tests; ADR-012 §6.5 ruling 5; store-api.md §Error model).
|
|
#[error("key missing (read_range/delete: get/stat signal absence with None)")]
|
|
Missing,
|
|
|
|
/// Put/get hash-check failure: content does not match the key
|
|
/// under the canonical derivation (store-api.md §Error model).
|
|
#[error("verification failed: content does not match key")]
|
|
Verification,
|
|
|
|
/// Backend media failure (store-api.md §Error model), covering
|
|
/// cursor and staged-put I/O errors surfaced through the same
|
|
/// trait error family — stringly because `std::io::Error` is not
|
|
/// stable across versions.
|
|
#[error("io: {0}")]
|
|
Io(String),
|
|
|
|
/// Direct delete refused: the key is protected — pinned,
|
|
/// registered-source-live, or protect-callback-named — and
|
|
/// nothing was deleted (ADR-005's delete-window arbitration at
|
|
/// delete time; ADR-012 §1/§4).
|
|
///
|
|
/// Never fires below the facade: arbitration answers flow up as
|
|
/// data, and the facade decides and owns the refusal (one enum,
|
|
/// both altitudes; store-api.md §Error model). Named `GcRefuse`
|
|
/// per ADR-012 §1 — there is no sweep-abort error.
|
|
#[error("direct delete refused: key protected (nothing deleted)")]
|
|
GcRefuse,
|
|
|
|
/// Malformed key bytes at the boundary — truncated, unknown
|
|
/// algorithm (ADR-002/hashing): rejects, never guesses
|
|
/// (store-api.md §Error model). KeyInvalid never fires at the
|
|
/// [`Backend`][crate::backend] trait boundary, where keys arrive
|
|
/// opaque (one enum, both altitudes).
|
|
#[error("invalid key bytes: truncated, unknown algorithm — rejects, never guesses")]
|
|
KeyInvalid,
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::Error;
|
|
|
|
/// Each distinguished variant constructs (store-api.md §Error
|
|
/// model: the five failure families, one enum).
|
|
#[test]
|
|
fn each_variant_constructs() {
|
|
let variants: [Error; 5] = [
|
|
Error::Missing,
|
|
Error::Verification,
|
|
Error::Io("cursor closed mid-read".to_owned()),
|
|
Error::GcRefuse,
|
|
Error::KeyInvalid,
|
|
];
|
|
let expected = [
|
|
"key missing",
|
|
"verification failed",
|
|
"io: cursor closed mid-read",
|
|
"direct delete refused",
|
|
"invalid key bytes",
|
|
];
|
|
for (err, text) in variants.iter().zip(expected) {
|
|
assert!(err.to_string().contains(text), "{err:?}");
|
|
}
|
|
}
|
|
|
|
/// The named design point (store-api.md §Error model): one enum,
|
|
/// both altitudes — the trait boundary's effective members plus
|
|
/// the facade re-exposure with `GcRefuse` added, carried by doc
|
|
/// comments and construction discipline rather than two types.
|
|
/// Facade behavioral enforcement is in place on the read paths
|
|
/// (`get`/`stat` never carry `Missing` — their both-tier misses
|
|
/// are `Ok(None)`, pinned by the store.rs read-path tests).
|
|
#[test]
|
|
fn one_enum_both_altitudes() {
|
|
let trait_effective: [Error; 4] = [
|
|
Error::Missing,
|
|
Error::Verification,
|
|
Error::Io("media".to_owned()),
|
|
Error::KeyInvalid,
|
|
];
|
|
assert_eq!(trait_effective.len(), 4);
|
|
let facade_surface: [Error; 5] = [
|
|
Error::Missing,
|
|
Error::Verification,
|
|
Error::Io("media".to_owned()),
|
|
Error::GcRefuse,
|
|
Error::KeyInvalid,
|
|
];
|
|
assert_eq!(facade_surface.len(), 5);
|
|
}
|
|
|
|
/// `get`/`stat` signal absence with `None` and never carry
|
|
/// `Missing` (ADR-012 §6.5 ruling 5; store-api.md §Error model)
|
|
/// — enforced behaviorally by the facade's `get`/`stat` returning
|
|
/// `Option` (the store.rs read-path tests pin the both-tier-miss
|
|
/// shapes); this test is the doc contract's in-module marker.
|
|
#[test]
|
|
fn missing_is_exclusive_to_err_shaped_ops() {
|
|
let absence_on_get: Option<String> = None;
|
|
assert_eq!(absence_on_get.map(|_| Error::Missing), None);
|
|
}
|
|
}
|