Skip to main content

qubit_fs/metadata/
file_system_capabilities.rs

1// =============================================================================
2//    Copyright (c) 2026 Haixing Hu.
3//
4//    SPDX-License-Identifier: Apache-2.0
5//
6//    Licensed under the Apache License, Version 2.0.
7// =============================================================================
8//! Filesystem capability support.
9
10use std::fmt::Debug;
11use std::fmt::Formatter;
12use std::fmt::Result as FmtResult;
13
14use crate::metadata::FileSystemCapability;
15use crate::metadata::FileSystemCapabilitySupport;
16
17const CAPABILITY_DEPENDENCIES: &[(FileSystemCapability, FileSystemCapability)] = &[
18    (FileSystemCapability::RangeRead, FileSystemCapability::Read),
19    (FileSystemCapability::ConditionalRead, FileSystemCapability::Read),
20    (FileSystemCapability::ChecksumValidation, FileSystemCapability::Read),
21    (FileSystemCapability::Append, FileSystemCapability::Write),
22    (FileSystemCapability::ConditionalWrite, FileSystemCapability::Write),
23    (FileSystemCapability::AtomicReplace, FileSystemCapability::Write),
24    (FileSystemCapability::DurableWrite, FileSystemCapability::Write),
25    (FileSystemCapability::RecursiveDelete, FileSystemCapability::Delete),
26    (FileSystemCapability::ConditionalDelete, FileSystemCapability::Delete),
27    (FileSystemCapability::AtomicRename, FileSystemCapability::Rename),
28    (FileSystemCapability::ServerSideCopy, FileSystemCapability::Copy),
29    (FileSystemCapability::AtomicFileCopy, FileSystemCapability::Copy),
30    (FileSystemCapability::AtomicTreeCopy, FileSystemCapability::Copy),
31    (FileSystemCapability::DurableFileCopy, FileSystemCapability::Copy),
32    (FileSystemCapability::DurableTreeCopy, FileSystemCapability::Copy),
33    (FileSystemCapability::DurableRename, FileSystemCapability::Rename),
34];
35
36/// Stable typed capability support for one configured filesystem.
37///
38/// Capabilities have one of two support strengths: conditional capabilities
39/// may be attempted and can still be rejected for an individual request, while
40/// guaranteed capabilities are promised for every valid request in scope.
41/// Derived capabilities must not be stronger than their base capability.
42///
43/// # Examples
44///
45/// ```
46/// use qubit_fs::metadata::{FileSystemCapabilities, FileSystemCapability,
47///     FileSystemCapabilitySupport};
48///
49/// let capabilities = FileSystemCapabilities::new()
50///     .with_conditional(FileSystemCapability::Read)
51///     .with_conditional(FileSystemCapability::RangeRead);
52/// assert_eq!(capabilities.support(FileSystemCapability::RangeRead),
53///     FileSystemCapabilitySupport::Conditional);
54/// assert_eq!(capabilities.support(FileSystemCapability::Write),
55///     FileSystemCapabilitySupport::Unsupported);
56/// assert!(capabilities.missing_dependency().is_none());
57///
58/// let invalid = FileSystemCapabilities::new()
59///     .with_guaranteed(FileSystemCapability::RangeRead)
60///     .with_conditional(FileSystemCapability::Read);
61/// assert_eq!(invalid.missing_dependency(), Some((
62///     FileSystemCapability::RangeRead, FileSystemCapability::Read)));
63/// ```
64#[derive(Clone, Copy, Eq, PartialEq)]
65pub struct FileSystemCapabilities {
66    /// Capabilities that can be attempted conditionally.
67    conditional: u128,
68    /// Capabilities guaranteed for every valid request in this scope.
69    guaranteed: u128,
70}
71
72impl FileSystemCapabilities {
73    /// Creates an empty capability set.
74    #[inline]
75    #[must_use]
76    pub const fn new() -> Self {
77        Self {
78            conditional: 0,
79            guaranteed: 0,
80        }
81    }
82
83    /// Returns a copy with one additional conditional capability.
84    #[inline]
85    #[must_use]
86    pub const fn with_conditional(self, capability: FileSystemCapability) -> Self {
87        self.set_support(capability, FileSystemCapabilitySupport::Conditional)
88    }
89
90    /// Returns a copy with one additional guaranteed capability.
91    #[inline]
92    #[must_use]
93    pub const fn with_guaranteed(self, capability: FileSystemCapability) -> Self {
94        self.set_support(capability, FileSystemCapabilitySupport::Guaranteed)
95    }
96
97    /// Replaces the support status of one capability.
98    #[inline]
99    #[must_use]
100    pub const fn set_support(mut self, capability: FileSystemCapability, support: FileSystemCapabilitySupport) -> Self {
101        let bit = capability.bit();
102        self.conditional &= !bit;
103        self.guaranteed &= !bit;
104        match support {
105            FileSystemCapabilitySupport::Unsupported => {}
106            FileSystemCapabilitySupport::Conditional => {
107                self.conditional |= bit;
108            }
109            FileSystemCapabilitySupport::Guaranteed => {
110                self.guaranteed |= bit;
111            }
112        }
113        self
114    }
115
116    /// Returns the support status of `capability`.
117    #[inline]
118    pub const fn support(&self, capability: FileSystemCapability) -> FileSystemCapabilitySupport {
119        let bit = capability.bit();
120        if self.guaranteed & bit != 0 {
121            FileSystemCapabilitySupport::Guaranteed
122        } else if self.conditional & bit != 0 {
123            FileSystemCapabilitySupport::Conditional
124        } else {
125            FileSystemCapabilitySupport::Unsupported
126        }
127    }
128
129    /// Returns whether the provider can attempt `capability`.
130    #[inline]
131    #[must_use]
132    pub const fn supports(&self, capability: FileSystemCapability) -> bool {
133        !matches!(self.support(capability), FileSystemCapabilitySupport::Unsupported)
134    }
135
136    /// Returns whether `capability` is guaranteed in this filesystem scope.
137    #[inline]
138    #[must_use]
139    pub const fn guarantees(&self, capability: FileSystemCapability) -> bool {
140        matches!(self.support(capability), FileSystemCapabilitySupport::Guaranteed)
141    }
142
143    /// Returns the number of advertised capabilities.
144    ///
145    /// # Returns
146    /// Number of set capability flags.
147    #[inline]
148    #[must_use]
149    pub const fn len(&self) -> usize {
150        (self.conditional | self.guaranteed).count_ones() as usize
151    }
152
153    /// Returns whether no capability is advertised.
154    ///
155    /// # Returns
156    /// `true` when the set contains no capability.
157    #[inline]
158    #[must_use]
159    pub const fn is_empty(&self) -> bool {
160        self.conditional == 0 && self.guaranteed == 0
161    }
162
163    /// Iterates advertised capabilities in stable discriminant order.
164    ///
165    /// # Returns
166    /// An iterator over every capability contained in this set.
167    #[inline]
168    pub fn iter(&self) -> impl Iterator<Item = FileSystemCapability> + '_ {
169        FileSystemCapability::ALL
170            .iter()
171            .copied()
172            .filter(|capability| self.supports(*capability))
173    }
174
175    /// Iterates advertised capabilities with their support status.
176    #[inline]
177    pub fn iter_with_support(&self) -> impl Iterator<Item = (FileSystemCapability, FileSystemCapabilitySupport)> + '_ {
178        self.iter().map(|capability| (capability, self.support(capability)))
179    }
180
181    /// Returns the first advertised capability whose required base capability
182    /// is absent.
183    ///
184    /// `None` means that every advertised derived capability has its required
185    /// base capability. The returned pair contains the derived capability
186    /// followed by the missing base capability.
187    ///
188    /// # Returns
189    /// `Some` with the first derived capability and missing base capability,
190    /// or `None` when no advertised dependency is missing.
191    #[inline]
192    #[must_use]
193    pub fn missing_dependency(&self) -> Option<(FileSystemCapability, FileSystemCapability)> {
194        CAPABILITY_DEPENDENCIES
195            .iter()
196            .copied()
197            .find(|(capability, dependency)| {
198                let capability_support = self.support(*capability);
199                let dependency_support = self.support(*dependency);
200                matches!(capability_support, FileSystemCapabilitySupport::Conditional)
201                    && matches!(dependency_support, FileSystemCapabilitySupport::Unsupported)
202                    || matches!(capability_support, FileSystemCapabilitySupport::Guaranteed)
203                        && !matches!(dependency_support, FileSystemCapabilitySupport::Guaranteed)
204            })
205    }
206}
207
208impl Default for FileSystemCapabilities {
209    /// Creates an empty capability set.
210    #[inline]
211    fn default() -> Self {
212        Self::new()
213    }
214}
215
216impl Debug for FileSystemCapabilities {
217    #[inline]
218    fn fmt(&self, formatter: &mut Formatter<'_>) -> FmtResult {
219        formatter.debug_map().entries(self.iter_with_support()).finish()
220    }
221}