Skip to main content

fslite_core/
options.rs

1use std::collections::BTreeMap;
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6use crate::{NodeKind, Revision, VirtualPath};
7
8/// The default maximum number of items requested in a page.
9pub const DEFAULT_PAGE_LIMIT: u32 = 100;
10
11/// An inclusive-start, exclusive-end logical byte range.
12#[non_exhaustive]
13#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
14pub struct ByteRange {
15    /// The first included byte offset.
16    pub start: u64,
17    /// The first excluded byte offset.
18    pub end: u64,
19}
20
21impl ByteRange {
22    /// Creates a logical byte range.
23    pub const fn new(start: u64, end: u64) -> Self {
24        Self { start, end }
25    }
26
27    /// Returns the range length, or zero when the range is reversed.
28    pub const fn len(self) -> u64 {
29        self.end.saturating_sub(self.start)
30    }
31
32    /// Returns whether the range selects no bytes.
33    pub const fn is_empty(self) -> bool {
34        self.start >= self.end
35    }
36}
37
38/// Cursor pagination parameters shared by listing and discovery operations.
39#[non_exhaustive]
40#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
41pub struct PageRequest {
42    /// An opaque continuation cursor returned by a prior page.
43    pub cursor: Option<String>,
44    /// The maximum number of items requested from the backend.
45    pub limit: u32,
46}
47
48impl PageRequest {
49    /// Sets the opaque continuation cursor.
50    #[must_use]
51    pub fn cursor(mut self, cursor: Option<String>) -> Self {
52        self.cursor = cursor;
53        self
54    }
55
56    /// Sets the requested maximum item count.
57    #[must_use]
58    pub const fn limit(mut self, limit: u32) -> Self {
59        self.limit = limit;
60        self
61    }
62}
63
64impl Default for PageRequest {
65    fn default() -> Self {
66        Self {
67            cursor: None,
68            limit: DEFAULT_PAGE_LIMIT,
69        }
70    }
71}
72
73/// Options controlling node metadata lookup.
74#[non_exhaustive]
75#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
76pub struct StatOptions {
77    /// Whether to resolve the final symbolic link.
78    pub follow_symlinks: bool,
79}
80
81impl StatOptions {
82    /// Sets whether to resolve the final symbolic link.
83    #[must_use]
84    pub const fn follow_symlinks(mut self, follow_symlinks: bool) -> Self {
85        self.follow_symlinks = follow_symlinks;
86        self
87    }
88}
89
90impl Default for StatOptions {
91    fn default() -> Self {
92        Self {
93            follow_symlinks: true,
94        }
95    }
96}
97
98/// Options controlling recursive tree traversal.
99#[non_exhaustive]
100#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
101pub struct TreeOptions {
102    /// The maximum depth below the requested path, subject to backend limits.
103    pub max_depth: Option<u32>,
104    /// Whether traversal follows symbolic links.
105    pub follow_symlinks: bool,
106}
107
108impl TreeOptions {
109    /// Sets the maximum traversal depth.
110    #[must_use]
111    pub const fn max_depth(mut self, max_depth: Option<u32>) -> Self {
112        self.max_depth = max_depth;
113        self
114    }
115
116    /// Sets whether traversal follows symbolic links.
117    #[must_use]
118    pub const fn follow_symlinks(mut self, follow_symlinks: bool) -> Self {
119        self.follow_symlinks = follow_symlinks;
120        self
121    }
122}
123
124/// Options controlling directory and symbolic-link creation.
125#[non_exhaustive]
126#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
127pub struct CreateOptions {
128    /// Whether missing parent directories are created.
129    pub parents: bool,
130    /// Whether an existing compatible node is accepted.
131    pub exist_ok: bool,
132    /// An optional optimistic precondition on an existing target.
133    pub expected_revision: Option<Revision>,
134}
135
136impl CreateOptions {
137    /// Sets whether missing parent directories are created.
138    #[must_use]
139    pub const fn parents(mut self, parents: bool) -> Self {
140        self.parents = parents;
141        self
142    }
143
144    /// Sets whether an existing compatible node is accepted.
145    #[must_use]
146    pub const fn exist_ok(mut self, exist_ok: bool) -> Self {
147        self.exist_ok = exist_ok;
148        self
149    }
150
151    /// Sets the optimistic revision precondition.
152    #[must_use]
153    pub const fn expected_revision(mut self, expected_revision: Option<Revision>) -> Self {
154        self.expected_revision = expected_revision;
155        self
156    }
157}
158
159/// Options controlling a streamed file read.
160#[non_exhaustive]
161#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
162pub struct ReadOptions {
163    /// The logical byte range to return, or the complete file when absent.
164    pub range: Option<ByteRange>,
165    /// Whether to resolve the final symbolic link.
166    pub follow_symlinks: bool,
167}
168
169impl ReadOptions {
170    /// Sets the logical byte range to return.
171    #[must_use]
172    pub const fn range(mut self, range: Option<ByteRange>) -> Self {
173        self.range = range;
174        self
175    }
176
177    /// Sets whether to resolve the final symbolic link.
178    #[must_use]
179    pub const fn follow_symlinks(mut self, follow_symlinks: bool) -> Self {
180        self.follow_symlinks = follow_symlinks;
181        self
182    }
183}
184
185impl Default for ReadOptions {
186    fn default() -> Self {
187        Self {
188            range: None,
189            follow_symlinks: true,
190        }
191    }
192}
193
194/// Options controlling a streamed file write.
195#[non_exhaustive]
196#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
197pub struct WriteOptions {
198    /// Whether the operation may create a missing regular file.
199    pub create: bool,
200    /// An optional optimistic precondition on an existing file.
201    pub expected_revision: Option<Revision>,
202}
203
204impl WriteOptions {
205    /// Sets whether a missing regular file may be created.
206    #[must_use]
207    pub const fn create(mut self, create: bool) -> Self {
208        self.create = create;
209        self
210    }
211
212    /// Sets the optimistic revision precondition.
213    #[must_use]
214    pub const fn expected_revision(mut self, expected_revision: Option<Revision>) -> Self {
215        self.expected_revision = expected_revision;
216        self
217    }
218
219    /// Requires the target file to exist and replaces its current contents.
220    pub const fn replace() -> Self {
221        Self {
222            create: false,
223            expected_revision: None,
224        }
225    }
226}
227
228impl Default for WriteOptions {
229    fn default() -> Self {
230        Self {
231            create: true,
232            expected_revision: None,
233        }
234    }
235}
236
237/// Common options for a mutation with no additional controls.
238#[non_exhaustive]
239#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
240pub struct MutationOptions {
241    /// An optional optimistic precondition on the target node.
242    pub expected_revision: Option<Revision>,
243}
244
245impl MutationOptions {
246    /// Sets the optimistic revision precondition.
247    #[must_use]
248    pub const fn expected_revision(mut self, expected_revision: Option<Revision>) -> Self {
249        self.expected_revision = expected_revision;
250        self
251    }
252}
253
254/// Options controlling a file touch operation.
255#[non_exhaustive]
256#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
257pub struct TouchOptions {
258    /// Whether touching a missing path creates an empty regular file.
259    pub create: bool,
260    /// An optional optimistic precondition on an existing file.
261    pub expected_revision: Option<Revision>,
262}
263
264impl TouchOptions {
265    /// Sets whether touching a missing path creates an empty regular file.
266    #[must_use]
267    pub const fn create(mut self, create: bool) -> Self {
268        self.create = create;
269        self
270    }
271
272    /// Sets the optimistic revision precondition.
273    #[must_use]
274    pub const fn expected_revision(mut self, expected_revision: Option<Revision>) -> Self {
275        self.expected_revision = expected_revision;
276        self
277    }
278}
279
280impl Default for TouchOptions {
281    fn default() -> Self {
282        Self {
283            create: true,
284            expected_revision: None,
285        }
286    }
287}
288
289/// Options controlling a copy operation.
290#[non_exhaustive]
291#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
292pub struct CopyOptions {
293    /// Whether directory descendants are copied.
294    pub recursive: bool,
295    /// Whether an existing destination may be replaced.
296    pub overwrite: bool,
297    /// An optional optimistic precondition on the source node.
298    pub expected_revision: Option<Revision>,
299}
300
301impl CopyOptions {
302    /// Sets whether directory descendants are copied.
303    #[must_use]
304    pub const fn recursive(mut self, recursive: bool) -> Self {
305        self.recursive = recursive;
306        self
307    }
308
309    /// Sets whether an existing destination may be replaced.
310    #[must_use]
311    pub const fn overwrite(mut self, overwrite: bool) -> Self {
312        self.overwrite = overwrite;
313        self
314    }
315
316    /// Sets the optimistic revision precondition.
317    #[must_use]
318    pub const fn expected_revision(mut self, expected_revision: Option<Revision>) -> Self {
319        self.expected_revision = expected_revision;
320        self
321    }
322}
323
324/// Options controlling a move operation.
325#[non_exhaustive]
326#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
327pub struct MoveOptions {
328    /// Whether an existing destination may be replaced.
329    pub overwrite: bool,
330    /// An optional optimistic precondition on the source node.
331    pub expected_revision: Option<Revision>,
332}
333
334impl MoveOptions {
335    /// Sets whether an existing destination may be replaced.
336    #[must_use]
337    pub const fn overwrite(mut self, overwrite: bool) -> Self {
338        self.overwrite = overwrite;
339        self
340    }
341
342    /// Sets the optimistic revision precondition.
343    #[must_use]
344    pub const fn expected_revision(mut self, expected_revision: Option<Revision>) -> Self {
345        self.expected_revision = expected_revision;
346        self
347    }
348}
349
350/// Options controlling permanent removal.
351#[non_exhaustive]
352#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
353pub struct RemoveOptions {
354    /// Whether a directory and all descendants may be removed.
355    pub recursive: bool,
356    /// An optional optimistic precondition on the target node.
357    pub expected_revision: Option<Revision>,
358}
359
360impl RemoveOptions {
361    /// Sets whether a directory and all descendants may be removed.
362    #[must_use]
363    pub const fn recursive(mut self, recursive: bool) -> Self {
364        self.recursive = recursive;
365        self
366    }
367
368    /// Sets the optimistic revision precondition.
369    #[must_use]
370    pub const fn expected_revision(mut self, expected_revision: Option<Revision>) -> Self {
371        self.expected_revision = expected_revision;
372        self
373    }
374}
375
376/// Metadata predicates for workspace-scoped node discovery.
377#[non_exhaustive]
378#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
379pub struct FindQuery {
380    /// The root beneath which matching nodes are considered.
381    pub root: VirtualPath,
382    /// An optional case-sensitive substring required in the node name.
383    pub name_contains: Option<String>,
384    /// An optional required node kind.
385    pub kind: Option<NodeKind>,
386    /// An optional inclusive lower bound on logical size.
387    pub min_logical_size: Option<u64>,
388    /// An optional inclusive upper bound on logical size.
389    pub max_logical_size: Option<u64>,
390    /// An optional exclusive lower bound on modification time.
391    pub modified_after_ms: Option<i64>,
392    /// An optional exclusive upper bound on modification time.
393    pub modified_before_ms: Option<i64>,
394    /// Attribute key/value pairs that every match must contain.
395    pub attributes: BTreeMap<String, Value>,
396}
397
398impl FindQuery {
399    /// Sets the root beneath which matching nodes are considered.
400    #[must_use]
401    pub fn root(mut self, root: VirtualPath) -> Self {
402        self.root = root;
403        self
404    }
405
406    /// Sets the optional case-sensitive node-name substring.
407    #[must_use]
408    pub fn name_contains(mut self, name_contains: Option<String>) -> Self {
409        self.name_contains = name_contains;
410        self
411    }
412
413    /// Sets the optional required node kind.
414    #[must_use]
415    pub const fn kind(mut self, kind: Option<NodeKind>) -> Self {
416        self.kind = kind;
417        self
418    }
419
420    /// Sets the optional inclusive lower bound on logical size.
421    #[must_use]
422    pub const fn min_logical_size(mut self, min_logical_size: Option<u64>) -> Self {
423        self.min_logical_size = min_logical_size;
424        self
425    }
426
427    /// Sets the optional inclusive upper bound on logical size.
428    #[must_use]
429    pub const fn max_logical_size(mut self, max_logical_size: Option<u64>) -> Self {
430        self.max_logical_size = max_logical_size;
431        self
432    }
433
434    /// Sets the optional exclusive lower modification-time bound.
435    #[must_use]
436    pub const fn modified_after_ms(mut self, modified_after_ms: Option<i64>) -> Self {
437        self.modified_after_ms = modified_after_ms;
438        self
439    }
440
441    /// Sets the optional exclusive upper modification-time bound.
442    #[must_use]
443    pub const fn modified_before_ms(mut self, modified_before_ms: Option<i64>) -> Self {
444        self.modified_before_ms = modified_before_ms;
445        self
446    }
447
448    /// Sets the required attribute key/value pairs.
449    #[must_use]
450    pub fn attributes(mut self, attributes: BTreeMap<String, Value>) -> Self {
451        self.attributes = attributes;
452        self
453    }
454}
455
456impl Default for FindQuery {
457    fn default() -> Self {
458        Self {
459            root: VirtualPath::root(),
460            name_contains: None,
461            kind: None,
462            min_logical_size: None,
463            max_logical_size: None,
464            modified_after_ms: None,
465            modified_before_ms: None,
466            attributes: BTreeMap::new(),
467        }
468    }
469}
470
471/// A bounded literal content-search request.
472#[non_exhaustive]
473#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
474pub struct ContentQuery {
475    /// The root beneath which regular files are searched.
476    pub root: VirtualPath,
477    /// The literal byte sequence to find.
478    pub needle: Vec<u8>,
479}
480
481impl ContentQuery {
482    /// Sets the root beneath which regular files are searched.
483    #[must_use]
484    pub fn root(mut self, root: VirtualPath) -> Self {
485        self.root = root;
486        self
487    }
488
489    /// Sets the literal byte sequence to find.
490    #[must_use]
491    pub fn needle(mut self, needle: Vec<u8>) -> Self {
492        self.needle = needle;
493        self
494    }
495}
496
497impl Default for ContentQuery {
498    fn default() -> Self {
499        Self {
500            root: VirtualPath::root(),
501            needle: Vec::new(),
502        }
503    }
504}