Skip to main content

acyclic_fs/kernel/
attribute_list.rs

1//! Bounded authenticated named-attribute pagination.
2
3use super::attribute_mutation::AttributeFormat;
4use super::persistent_pagination;
5use super::{AttributeEntry, AttributeName, CanonicalDecodeError, DecodeLimits};
6use crate::async_storage::AsyncObjectStore;
7use crate::cancellation::CancellationToken;
8use crate::performance::{OperationFailure, WorkBudget, WorkCounters, WorkError};
9use crate::speculation::{ResidencyHint, ResidencyReason};
10use crate::storage::{ObjectId, ObjectStoreError};
11use thiserror::Error;
12
13/// One bounded ordered named-attribute page.
14#[derive(Clone, Debug, Eq, PartialEq)]
15pub struct AttributeListing {
16    /// Entries strictly after the supplied exact cursor.
17    pub entries: Vec<AttributeEntry>,
18    /// Whether at least one additional entry exists.
19    pub has_more: bool,
20    /// Exact unvisited metadata successor exposed by authenticated traversal.
21    pub next_residency: Option<ResidencyHint>,
22    /// Exact backend, traversal, copy, and logical-allocation work.
23    pub work: WorkCounters,
24}
25
26/// Lists one bounded attribute page through the shared persistent-tree cursor.
27///
28/// # Errors
29///
30/// Fails closed on malformed routing, cycles, invalid bounds, storage failure,
31/// or work outside the admitted budget.
32pub fn list_attributes<S: crate::ImmediateObjectStore>(
33    store: &S,
34    root: ObjectId,
35    after: Option<&AttributeName>,
36    maximum_entries: u32,
37    limits: DecodeLimits,
38    budget: WorkBudget,
39) -> Result<AttributeListing, AttributeListFailure> {
40    persistent_pagination::paginate::<S, AttributeFormat>(
41        store,
42        root,
43        after,
44        maximum_entries,
45        limits,
46        budget,
47    )
48    .map(to_listing)
49    .map_err(map_failure)
50}
51
52/// Asynchronously executes the same cursor machine as [`list_attributes`].
53///
54/// # Errors
55///
56/// Returns the same typed failures, including cancellation before every
57/// backend boundary.
58pub async fn list_attributes_async<S: AsyncObjectStore>(
59    store: &S,
60    root: ObjectId,
61    after: Option<&AttributeName>,
62    maximum_entries: u32,
63    limits: DecodeLimits,
64    budget: WorkBudget,
65    cancellation: &CancellationToken,
66) -> Result<AttributeListing, AttributeListFailure> {
67    persistent_pagination::paginate_async::<S, AttributeFormat>(
68        store,
69        root,
70        after,
71        maximum_entries,
72        limits,
73        budget,
74        cancellation,
75    )
76    .await
77    .map(to_listing)
78    .map_err(map_failure)
79}
80
81fn to_listing(receipt: persistent_pagination::Receipt<AttributeEntry>) -> AttributeListing {
82    AttributeListing {
83        entries: receipt.values,
84        has_more: receipt.has_more,
85        next_residency: receipt.next_request.map(|request| ResidencyHint {
86            request,
87            reason: ResidencyReason::MetadataSuccessor,
88        }),
89        work: receipt.work,
90    }
91}
92
93/// Named-attribute pagination failure retaining exact spent work.
94pub type AttributeListFailure = OperationFailure<AttributeListError>;
95
96fn map_failure(failure: persistent_pagination::Failure) -> AttributeListFailure {
97    OperationFailure::new(map_error(failure.error), *failure.work)
98}
99
100fn map_error(error: persistent_pagination::Error) -> AttributeListError {
101    match error {
102        persistent_pagination::Error::Cancelled => AttributeListError::Cancelled,
103        persistent_pagination::Error::ZeroLimit => AttributeListError::ZeroLimit,
104        persistent_pagination::Error::LimitOverflow => AttributeListError::LimitOverflow,
105        persistent_pagination::Error::WrongRootKind => AttributeListError::WrongRootKind,
106        persistent_pagination::Error::InvalidLimits => AttributeListError::InvalidLimits,
107        persistent_pagination::Error::HeightExceeded => AttributeListError::HeightExceeded,
108        persistent_pagination::Error::CycleOrAlias => AttributeListError::CycleOrAlias,
109        persistent_pagination::Error::ChildBoundsMismatch => {
110            AttributeListError::ChildBoundsMismatch
111        }
112        persistent_pagination::Error::TraversalState => AttributeListError::TraversalState,
113        persistent_pagination::Error::AllocationFailed => AttributeListError::AllocationFailed,
114        persistent_pagination::Error::Storage(error) => error.into(),
115        persistent_pagination::Error::Decode(error) => error.into(),
116        persistent_pagination::Error::Work(error) => error.into(),
117    }
118}
119
120/// Authenticated bounded named-attribute pagination failures.
121#[derive(Debug, Error)]
122pub enum AttributeListError {
123    /// Cooperative cancellation occurred before the next backend boundary.
124    #[error("attribute listing was cancelled")]
125    Cancelled,
126    /// Page bound must be positive.
127    #[error("attribute page limit must be non-zero")]
128    ZeroLimit,
129    /// Page-bound allocation overflowed this target.
130    #[error("attribute page limit cannot be represented")]
131    LimitOverflow,
132    /// Root is not an attribute page.
133    #[error("attribute root is not an attribute page")]
134    WrongRootKind,
135    /// Decode/traversal limits are internally inconsistent.
136    #[error("attribute listing limits are invalid")]
137    InvalidLimits,
138    /// Traversal exceeded the admitted height.
139    #[error("attribute tree exceeds its admitted height")]
140    HeightExceeded,
141    /// Child graph references an ancestor or aliases a traversed page.
142    #[error("attribute tree contains a cycle or alias")]
143    CycleOrAlias,
144    /// Parent routing bounds disagree with a child page.
145    #[error("attribute child bounds do not match its page")]
146    ChildBoundsMismatch,
147    /// The private traversal reached an impossible state.
148    #[error("attribute listing transition state is invalid")]
149    TraversalState,
150    /// A bounded scratch allocation could not be represented or reserved.
151    #[error("attribute listing allocation failed")]
152    AllocationFailed,
153    /// Immutable object backend failed.
154    #[error(transparent)]
155    Storage(#[from] ObjectStoreError),
156    /// Canonical attribute page failed decoding.
157    #[error(transparent)]
158    Decode(#[from] CanonicalDecodeError),
159    /// Exact work exceeded or overflowed its budget.
160    #[error(transparent)]
161    Work(#[from] WorkError),
162}
163
164#[cfg(all(test, feature = "memory"))]
165#[path = "tests/attribute_list.rs"]
166mod tests;