Skip to main content

structfs_handles/
conformance.rs

1//! Conformance checks for the handle-store protocol.
2//!
3//! Run these from a handle store's test suite to certify it follows the
4//! `outstanding/{id}` rules — whether it's built on [`crate::HandleStore`]
5//! or hand-rolled. Each check panics with a descriptive message on
6//! violation.
7
8use structfs_core_store::{DetachedStore, Error, Path, Record, Value};
9
10fn root() -> Path {
11    Path::parse("").unwrap()
12}
13
14/// Run every handle-protocol check against a fresh store.
15///
16/// `request` is a value the store accepts as a mint request.
17pub async fn check_handle_conventions<S: DetachedStore>(store: &mut S, request: Value) {
18    check_mint_returns_handle_path(store, request.clone()).await;
19    check_overwrite_conflicts(store, request.clone()).await;
20    check_release_and_absence(store, request.clone()).await;
21    check_release_idempotent(store, request).await;
22}
23
24/// A root write mints a handle at `outstanding/{id}`.
25pub async fn check_mint_returns_handle_path<S: DetachedStore>(store: &mut S, request: Value) {
26    let path = store
27        .write_detached(&root(), Record::parsed(request))
28        .await
29        .expect("mint write failed");
30    assert!(
31        path.len() == 2 && &path[0] == "outstanding" && path[1].parse::<u64>().is_ok(),
32        "mint must return outstanding/{{id}}, got: {}",
33        path
34    );
35    let record = store
36        .read_detached(&path)
37        .await
38        .expect("handle read failed");
39    assert!(record.is_some(), "a live handle must be readable");
40}
41
42/// A non-Null write directly to a handle is a conflict.
43pub async fn check_overwrite_conflicts<S: DetachedStore>(store: &mut S, request: Value) {
44    let path = store
45        .write_detached(&root(), Record::parsed(request))
46        .await
47        .expect("mint write failed");
48    let err = store
49        .write_detached(&path, Record::parsed(Value::from("clobber")))
50        .await
51        .expect_err("overwriting a live handle must fail");
52    assert!(
53        matches!(err, Error::Conflict { .. }),
54        "handle overwrite must be Error::Conflict, got: {}",
55        err
56    );
57}
58
59/// A Null write releases the handle; released handles read as absent.
60pub async fn check_release_and_absence<S: DetachedStore>(store: &mut S, request: Value) {
61    let path = store
62        .write_detached(&root(), Record::parsed(request))
63        .await
64        .expect("mint write failed");
65    store
66        .write_detached(&path, Record::parsed(Value::Null))
67        .await
68        .expect("Null write must release the handle");
69    let record = store
70        .read_detached(&path)
71        .await
72        .expect("post-release read failed");
73    assert!(record.is_none(), "a released handle must read as absent");
74}
75
76/// Releasing twice, or releasing an unknown handle, is a no-op.
77pub async fn check_release_idempotent<S: DetachedStore>(store: &mut S, request: Value) {
78    let path = store
79        .write_detached(&root(), Record::parsed(request))
80        .await
81        .expect("mint write failed");
82    store
83        .write_detached(&path, Record::parsed(Value::Null))
84        .await
85        .expect("first release failed");
86    store
87        .write_detached(&path, Record::parsed(Value::Null))
88        .await
89        .expect("double release must be a no-op");
90    store
91        .write_detached(
92            &Path::parse("outstanding/18446744073709551614").unwrap(),
93            Record::parsed(Value::Null),
94        )
95        .await
96        .expect("releasing an unknown handle must be a no-op");
97}