Skip to main content

qubit_fs/metadata/
file_metadata.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//! File metadata model.
9
10use std::time::SystemTime;
11
12use crate::metadata::Checksum;
13use crate::metadata::FileKind;
14use crate::metadata::NonSensitiveMetadata;
15use crate::metadata::ResourceVersion;
16use crate::metadata::UserMetadata;
17
18/// Stable and extensible metadata for one filesystem resource.
19///
20/// # Examples
21///
22/// ```rust
23/// use qubit_fs::metadata::{FileKind, FileMetadata};
24///
25/// let metadata = FileMetadata::new(FileKind::File).with_len(Some(42));
26/// assert_eq!(Some(42), metadata.len());
27/// ```
28#[non_exhaustive]
29#[derive(Clone, Debug, PartialEq)]
30pub struct FileMetadata {
31    /// Provider-neutral resource kind.
32    kind: FileKind,
33    /// Byte length when known.
34    len: Option<u64>,
35    /// Last modification time when known.
36    modified_at: Option<SystemTime>,
37    /// Creation time when known.
38    created_at: Option<SystemTime>,
39    /// Last access time when known.
40    accessed_at: Option<SystemTime>,
41    /// Provider version or HTTP-style ETag when known.
42    etag: Option<ResourceVersion>,
43    /// Content type when known.
44    content_type: Option<String>,
45    /// Content checksum when known.
46    checksum: Option<Checksum>,
47    /// User-defined metadata with validated non-sensitive structural keys.
48    user_metadata: NonSensitiveMetadata,
49    /// Provider-native metadata with validated non-sensitive structural keys.
50    provider_metadata: NonSensitiveMetadata,
51}
52
53impl FileMetadata {
54    /// Creates metadata with only a file kind.
55    ///
56    /// # Parameters
57    /// - `kind`: Provider-neutral resource kind.
58    ///
59    /// # Returns
60    /// Metadata value with unknown optional fields.
61    #[inline]
62    #[must_use]
63    pub fn new(kind: FileKind) -> Self {
64        Self {
65            kind,
66            len: None,
67            modified_at: None,
68            created_at: None,
69            accessed_at: None,
70            etag: None,
71            content_type: None,
72            checksum: None,
73            user_metadata: NonSensitiveMetadata::new(),
74            provider_metadata: NonSensitiveMetadata::new(),
75        }
76    }
77
78    /// Returns the provider-neutral resource kind.
79    #[inline]
80    #[must_use]
81    pub const fn kind(&self) -> &FileKind {
82        &self.kind
83    }
84
85    /// Returns the known byte length, if any.
86    #[inline]
87    #[must_use]
88    pub const fn len(&self) -> Option<u64> {
89        self.len
90    }
91
92    /// Returns whether the known byte length is zero.
93    #[inline]
94    #[must_use]
95    pub const fn is_empty(&self) -> bool {
96        matches!(self.len, Some(0))
97    }
98
99    /// Returns the last modification time, if known.
100    #[inline]
101    #[must_use]
102    pub const fn modified_at(&self) -> Option<SystemTime> {
103        self.modified_at
104    }
105
106    /// Returns the creation time, if known.
107    #[inline]
108    #[must_use]
109    pub const fn created_at(&self) -> Option<SystemTime> {
110        self.created_at
111    }
112
113    /// Returns the last access time, if known.
114    #[inline]
115    #[must_use]
116    pub const fn accessed_at(&self) -> Option<SystemTime> {
117        self.accessed_at
118    }
119
120    /// Returns the provider version or ETag, if known.
121    #[inline]
122    #[must_use]
123    pub const fn etag(&self) -> Option<&ResourceVersion> {
124        self.etag.as_ref()
125    }
126
127    /// Returns the content type, if known.
128    ///
129    /// # Returns
130    /// `Some` with the content type borrowed from this snapshot, or `None` when
131    /// the provider did not report one.
132    #[inline]
133    #[must_use]
134    pub fn content_type(&self) -> Option<&str> {
135        self.content_type.as_deref()
136    }
137
138    /// Returns the content checksum, if known.
139    #[inline]
140    #[must_use]
141    pub const fn checksum(&self) -> Option<&Checksum> {
142        self.checksum.as_ref()
143    }
144
145    /// Returns validated user-defined metadata.
146    #[inline]
147    #[must_use]
148    pub const fn user_metadata(&self) -> &NonSensitiveMetadata {
149        &self.user_metadata
150    }
151
152    /// Returns validated provider-native metadata.
153    #[inline]
154    #[must_use]
155    pub const fn provider_metadata(&self) -> &NonSensitiveMetadata {
156        &self.provider_metadata
157    }
158
159    /// Replaces the resource kind.
160    #[inline]
161    #[must_use]
162    pub fn with_kind(mut self, kind: FileKind) -> Self {
163        self.kind = kind;
164        self
165    }
166
167    /// Replaces the known byte length.
168    #[inline]
169    #[must_use]
170    pub fn with_len(mut self, len: Option<u64>) -> Self {
171        self.len = len;
172        self
173    }
174
175    /// Replaces the last modification time.
176    #[inline]
177    #[must_use]
178    pub fn with_modified_at(mut self, value: Option<SystemTime>) -> Self {
179        self.modified_at = value;
180        self
181    }
182
183    /// Replaces the creation time.
184    #[inline]
185    #[must_use]
186    pub fn with_created_at(mut self, value: Option<SystemTime>) -> Self {
187        self.created_at = value;
188        self
189    }
190
191    /// Replaces the last access time.
192    #[inline]
193    #[must_use]
194    pub fn with_accessed_at(mut self, value: Option<SystemTime>) -> Self {
195        self.accessed_at = value;
196        self
197    }
198
199    /// Replaces the provider version or ETag.
200    #[inline]
201    #[must_use]
202    pub fn with_etag(mut self, value: Option<ResourceVersion>) -> Self {
203        self.etag = value;
204        self
205    }
206
207    /// Replaces the content type.
208    #[inline]
209    #[must_use]
210    pub fn with_content_type(mut self, value: Option<String>) -> Self {
211        self.content_type = value;
212        self
213    }
214
215    /// Replaces the content checksum.
216    #[inline]
217    #[must_use]
218    pub fn with_checksum(mut self, value: Option<Checksum>) -> Self {
219        self.checksum = value;
220        self
221    }
222
223    /// Replaces user-defined metadata that has already passed key validation.
224    #[inline]
225    #[must_use]
226    pub fn with_user_metadata(mut self, metadata: UserMetadata) -> Self {
227        self.user_metadata = NonSensitiveMetadata::from(metadata);
228        self
229    }
230
231    /// Replaces provider-native metadata that has already passed key
232    /// validation.
233    #[inline]
234    #[must_use]
235    pub fn with_provider_metadata(mut self, metadata: UserMetadata) -> Self {
236        self.provider_metadata = NonSensitiveMetadata::from(metadata);
237        self
238    }
239
240    /// Tells whether this metadata describes a directory-like resource.
241    ///
242    /// # Returns
243    /// `true` for directories and prefixes.
244    #[inline]
245    #[must_use]
246    pub fn is_directory_like(&self) -> bool {
247        matches!(self.kind, FileKind::Directory | FileKind::Prefix)
248    }
249
250    /// Tells whether this metadata describes a file-like resource.
251    ///
252    /// # Returns
253    /// `true` for regular files and object-store objects.
254    #[inline]
255    #[must_use]
256    pub fn is_file_like(&self) -> bool {
257        matches!(self.kind, FileKind::File | FileKind::Object)
258    }
259}