Skip to main content

vfsi_core/api/
support.rs

1use super::*;
2use std::path::PathBuf;
3
4/// Default payload limit for APIs that allocate and return complete contents.
5pub const DEFAULT_READ_MAX_BYTES: usize = 16 * 1024 * 1024;
6
7/// Default size of each chunk delivered by the bounded single-file stream API.
8pub const DEFAULT_READ_STREAM_CHUNK_BYTES: usize = 1024 * 1024;
9
10/// Default aggregate payload limit for complete-file vector reads.
11pub const DEFAULT_READ_ALLV_MAX_TOTAL_BYTES: usize = DEFAULT_READ_MAX_BYTES;
12
13/// Tuning options for bounded single-file streaming reads.
14#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15pub struct StreamOptions {
16    chunk_size: std::num::NonZeroUsize,
17}
18
19impl StreamOptions {
20    pub const fn new() -> Self {
21        Self {
22            chunk_size: std::num::NonZeroUsize::new(DEFAULT_READ_STREAM_CHUNK_BYTES).unwrap(),
23        }
24    }
25
26    /// Set the nonzero maximum bytes delivered to the callback at once.
27    ///
28    /// ```compile_fail
29    /// use vfsi_core::api::StreamOptions;
30    /// let options = StreamOptions::new().chunk_size(0);
31    /// ```
32    ///
33    /// NFS and other backends may return smaller chunks due to negotiated
34    /// protocol limits. A larger setting does not bypass those limits.
35    pub const fn chunk_size(mut self, bytes: std::num::NonZeroUsize) -> Self {
36        self.chunk_size = bytes;
37        self
38    }
39
40    pub const fn chunk_size_bytes(self) -> usize {
41        self.chunk_size.get()
42    }
43}
44
45impl Default for StreamOptions {
46    fn default() -> Self {
47        Self::new()
48    }
49}
50
51/// Default maximum number of entries returned by allocating directory APIs.
52pub const DEFAULT_DIRECTORY_MAX_ENTRIES: usize = 100_000;
53
54/// Default combined path-storage budget for allocating directory APIs.
55pub const DEFAULT_DIRECTORY_MAX_PATH_BYTES: usize = 16 * 1024 * 1024;
56
57/// Default recursion depth for recursive walks.
58pub const DEFAULT_WALK_MAX_DEPTH: usize = 128;
59
60/// Compact traversal depth: 0 through 200 are finite, larger inputs are unlimited.
61/// The root has depth zero. Unlimited depth does not disable entry/byte budgets.
62/// Both this type and `Option<DepthLimit>` occupy one byte.
63#[repr(transparent)]
64#[derive(Clone, Copy, Debug, PartialEq, Eq)]
65pub struct DepthLimit(std::num::NonZeroU8);
66
67impl DepthLimit {
68    /// Normalize a requested depth. Every value above 200 means unlimited.
69    pub const fn new(depth: usize) -> Self {
70        let encoded = if depth <= 200 { (depth + 1) as u8 } else { 202 };
71        Self(std::num::NonZeroU8::new(encoded).expect("depth encoding is nonzero"))
72    }
73    /// Disable the depth limit while retaining other traversal budgets.
74    pub const fn unlimited() -> Self {
75        Self::new(usize::MAX)
76    }
77    /// Effective finite limit, or `usize::MAX` for unlimited traversal.
78    pub const fn get(self) -> usize {
79        if self.is_unlimited() {
80            usize::MAX
81        } else {
82            self.0.get() as usize - 1
83        }
84    }
85    /// Whether traversal has no depth limit.
86    pub const fn is_unlimited(self) -> bool {
87        self.0.get() == 202
88    }
89}
90
91/// Default aggregate payload limit for owned vector reads.
92pub const DEFAULT_READV_MAX_TOTAL_BYTES: usize = DEFAULT_READ_MAX_BYTES;
93
94/// Application result for one positional vector read, in request order.
95#[derive(Debug, Clone, PartialEq, Eq)]
96pub struct FsReadResult {
97    pub offset: u64,
98    pub data: Vec<u8>,
99    pub eof: bool,
100}
101
102/// Application result for one read into caller storage, in request order.
103#[derive(Debug, Clone, Copy, PartialEq, Eq)]
104pub struct ReadIntoResult {
105    pub offset: u64,
106    pub read: usize,
107    pub eof: bool,
108}
109
110/// Application result for one vector write, in request order.
111/// For completed append writes, `offset + written` is the last reported end;
112/// concurrent writers can interleave between short-write waves.
113#[derive(Debug, Clone, Copy, PartialEq, Eq)]
114pub struct WriteResult {
115    pub offset: u64,
116    pub written: usize,
117    pub stable: bool,
118}
119
120/// Completion of a streaming read. Stopping does not mean EOF.
121#[derive(Debug, Clone, Copy, PartialEq, Eq)]
122pub enum StreamCompletion {
123    Complete,
124    Stopped { next_offset: u64 },
125}
126
127/// Completion of a callback-based directory traversal.
128#[derive(Debug, Clone, Copy, PartialEq, Eq)]
129pub enum TraversalCompletion {
130    Complete,
131    Stopped,
132}
133
134/// Default resource policy for a client. Per-call options override it.
135/// Byte limits bound logical payload, not allocator capacity or RPC overhead.
136#[derive(Debug, Clone, Copy, PartialEq, Eq)]
137pub struct ResourceLimits {
138    /// Maximum collected scalar payload or aggregate vector read buffers.
139    /// Not a process-memory cap; standard `Read::read_to_end` and explicit
140    /// positional file reads remain caller-managed.
141    max_read_bytes: usize,
142    /// Default request size for client `read_stream`, not file `Read` or pools.
143    stream_chunk_bytes: std::num::NonZeroUsize,
144    /// Aggregate entries delivered/collected by a directory call or tree walk.
145    max_directory_entries: usize,
146    /// Aggregate logical path bytes, excluding allocator and metadata overhead.
147    max_directory_path_bytes: usize,
148    /// Maximum descent depth; the starting directory is depth zero.
149    max_walk_depth: usize,
150}
151
152impl Default for ResourceLimits {
153    fn default() -> Self {
154        Self {
155            max_read_bytes: DEFAULT_READ_MAX_BYTES,
156            stream_chunk_bytes: std::num::NonZeroUsize::new(DEFAULT_READ_STREAM_CHUNK_BYTES)
157                .unwrap(),
158            max_directory_entries: crate::api::DEFAULT_DIRECTORY_MAX_ENTRIES,
159            max_directory_path_bytes: crate::api::DEFAULT_DIRECTORY_MAX_PATH_BYTES,
160            max_walk_depth: crate::api::DEFAULT_WALK_MAX_DEPTH,
161        }
162    }
163}
164
165impl ResourceLimits {
166    pub fn new() -> Self {
167        Self::default()
168    }
169    /// Set the collected read budget; zero permits only empty payloads.
170    pub const fn max_read_bytes(mut self, value: usize) -> Self {
171        self.max_read_bytes = value;
172        self
173    }
174    /// Read the configured policy value.
175    pub const fn read_byte_limit(self) -> usize {
176        self.max_read_bytes
177    }
178    /// Set the default streaming chunk size. Nonzero chunks guarantee progress.
179    pub const fn stream_chunk_bytes(mut self, value: std::num::NonZeroUsize) -> Self {
180        self.stream_chunk_bytes = value;
181        self
182    }
183    /// Read the configured policy value.
184    pub const fn stream_chunk_size(self) -> usize {
185        self.stream_chunk_bytes.get()
186    }
187    /// Set the aggregate directory entry budget; zero is a valid budget.
188    pub const fn max_directory_entries(mut self, value: usize) -> Self {
189        self.max_directory_entries = value;
190        self
191    }
192    /// Read the configured policy value.
193    pub const fn directory_entry_limit(self) -> usize {
194        self.max_directory_entries
195    }
196    /// Set the aggregate path byte budget; zero is a valid budget.
197    pub const fn max_directory_path_bytes(mut self, value: usize) -> Self {
198        self.max_directory_path_bytes = value;
199        self
200    }
201    /// Read the configured policy value.
202    pub const fn directory_path_byte_limit(self) -> usize {
203        self.max_directory_path_bytes
204    }
205    /// Set maximum descent depth; zero includes only the starting directory.
206    pub const fn max_walk_depth(mut self, value: usize) -> Self {
207        self.max_walk_depth = value;
208        self
209    }
210    /// Read the configured policy value.
211    pub const fn walk_depth_limit(self) -> usize {
212        self.max_walk_depth
213    }
214
215    pub fn directory_options(self) -> ListDirOptions {
216        ListDirOptions::new()
217            .max_entries(self.max_directory_entries)
218            .max_path_bytes(self.max_directory_path_bytes)
219    }
220
221    pub fn walk_options(self) -> crate::api::ListDirOptions {
222        crate::api::ListDirOptions::new()
223            .recursive(true)
224            .max_entries(self.max_directory_entries)
225            .max_path_bytes(self.max_directory_path_bytes)
226            .max_depth(self.max_walk_depth)
227    }
228}
229
230/// One directory and its entries, with attributes fetched during enumeration.
231#[derive(Clone, Debug, PartialEq, Eq)]
232pub struct DirectoryListing {
233    pub path: PathBuf,
234    pub entries: Vec<DirEntry>,
235}