qubit_fs/write/
write_options.rs1use crate::error::FsError;
11use crate::error::FsErrorKind;
12use crate::error::FsOperation;
13use crate::metadata::AtomicityRequirement;
14use crate::metadata::Checksum;
15use crate::metadata::DurabilityRequirement;
16use crate::metadata::FileSystemCapabilities;
17use crate::metadata::FileSystemCapability;
18use crate::metadata::NonSensitiveMetadata;
19use crate::metadata::UserMetadata;
20use crate::write::WriteDisposition;
21use crate::write::WritePrecondition;
22
23#[non_exhaustive]
34#[derive(Clone, Debug, PartialEq)]
35pub struct WriteOptions {
36 create_parent: bool,
38 disposition: WriteDisposition,
40 atomicity: AtomicityRequirement,
42 durability: DurabilityRequirement,
44 precondition: WritePrecondition,
46 content_type: Option<String>,
48 user_metadata: NonSensitiveMetadata,
50 checksum: Option<Checksum>,
52}
53
54impl Default for WriteOptions {
55 #[inline]
56 fn default() -> Self {
57 Self {
58 create_parent: false,
59 disposition: WriteDisposition::default(),
60 atomicity: AtomicityRequirement::default(),
61 durability: DurabilityRequirement::default(),
62 precondition: WritePrecondition::default(),
63 content_type: None,
64 user_metadata: NonSensitiveMetadata::new(),
65 checksum: None,
66 }
67 }
68}
69
70impl WriteOptions {
71 #[inline]
73 #[must_use]
74 pub const fn with_create_parent(mut self, create: bool) -> Self {
75 self.create_parent = create;
76 self
77 }
78
79 #[inline]
81 #[must_use]
82 pub const fn create_parent(&self) -> bool {
83 self.create_parent
84 }
85
86 #[inline]
88 #[must_use]
89 pub const fn with_disposition(mut self, disposition: WriteDisposition) -> Self {
90 self.disposition = disposition;
91 self
92 }
93
94 #[inline]
96 #[must_use]
97 pub const fn disposition(&self) -> WriteDisposition {
98 self.disposition
99 }
100
101 #[inline]
103 #[must_use]
104 pub const fn with_atomicity(mut self, atomicity: AtomicityRequirement) -> Self {
105 self.atomicity = atomicity;
106 self
107 }
108
109 #[inline]
111 #[must_use]
112 pub const fn atomicity(&self) -> AtomicityRequirement {
113 self.atomicity
114 }
115
116 #[inline]
118 #[must_use]
119 pub const fn with_durability(mut self, durability: DurabilityRequirement) -> Self {
120 self.durability = durability;
121 self
122 }
123
124 #[inline]
126 #[must_use]
127 pub const fn durability(&self) -> DurabilityRequirement {
128 self.durability
129 }
130
131 #[inline]
133 #[must_use]
134 pub fn with_precondition(mut self, precondition: WritePrecondition) -> Self {
135 self.precondition = precondition;
136 self
137 }
138
139 #[inline]
141 #[must_use]
142 pub const fn precondition(&self) -> &WritePrecondition {
143 &self.precondition
144 }
145
146 #[inline]
148 #[must_use]
149 pub fn with_content_type(mut self, content_type: Option<String>) -> Self {
150 self.content_type = content_type;
151 self
152 }
153
154 #[inline]
156 #[must_use]
157 pub fn content_type(&self) -> Option<&str> {
158 self.content_type.as_deref()
159 }
160
161 #[inline]
163 #[must_use]
164 pub const fn user_metadata(&self) -> &NonSensitiveMetadata {
165 &self.user_metadata
166 }
167
168 #[inline]
170 #[must_use]
171 pub fn with_checksum(mut self, checksum: Option<Checksum>) -> Self {
172 self.checksum = checksum;
173 self
174 }
175
176 #[inline]
178 #[must_use]
179 pub const fn checksum(&self) -> Option<&Checksum> {
180 self.checksum.as_ref()
181 }
182
183 #[inline]
185 #[must_use]
186 pub fn with_user_metadata(mut self, metadata: UserMetadata) -> Self {
187 self.user_metadata = NonSensitiveMetadata::from(metadata);
188 self
189 }
190
191 #[inline]
198 pub fn validate(&self) -> Result<(), FsError> {
199 if self.disposition == WriteDisposition::Append
200 && (self.atomicity == AtomicityRequirement::Required || self.precondition != WritePrecondition::None)
201 {
202 return Err(FsError::new(
203 FsErrorKind::InvalidOptions,
204 FsOperation::OpenWriter,
205 "append cannot require atomic publication or a destination version",
206 ));
207 }
208 if self.disposition == WriteDisposition::CreateNew
209 && matches!(&self.precondition, WritePrecondition::IfMatch(_))
210 {
211 return Err(FsError::new(
212 FsErrorKind::InvalidOptions,
213 FsOperation::OpenWriter,
214 "create-new cannot require an existing destination version",
215 ));
216 }
217 Ok(())
218 }
219
220 pub fn validate_against(&self, capabilities: FileSystemCapabilities) -> Result<(), FsError> {
232 self.validate()?;
233 if self.disposition == WriteDisposition::Append && !capabilities.supports(FileSystemCapability::Append) {
234 return Err(missing_requirement(
235 FileSystemCapability::Append,
236 "append writes are required but not supported",
237 ));
238 }
239 if self.precondition != WritePrecondition::None
240 && !capabilities.supports(FileSystemCapability::ConditionalWrite)
241 {
242 return Err(missing_requirement(
243 FileSystemCapability::ConditionalWrite,
244 "conditional writes are required but not supported",
245 ));
246 }
247 if self.atomicity == AtomicityRequirement::Required
248 && !capabilities.supports(FileSystemCapability::AtomicReplace)
249 {
250 return Err(missing_requirement(
251 FileSystemCapability::AtomicReplace,
252 "atomic write publication is required but not supported",
253 ));
254 }
255 if self.durability == DurabilityRequirement::Required
256 && !capabilities.supports(FileSystemCapability::DurableWrite)
257 {
258 return Err(missing_requirement(
259 FileSystemCapability::DurableWrite,
260 "durable write publication is required but not supported",
261 ));
262 }
263 Ok(())
264 }
265}
266
267fn missing_requirement(capability: FileSystemCapability, message: &str) -> FsError {
269 FsError::new(FsErrorKind::RequirementNotMet, FsOperation::OpenWriter, message).with_required_capability(capability)
270}
271
272#[cfg(test)]
273mod tests {
274 use std::hint::black_box;
275
276 use super::WriteOptions;
277 use crate::metadata::Checksum;
278 use crate::metadata::ChecksumAlgorithm;
279 use crate::metadata::NonSensitiveMetadata;
280 use crate::write::WriteDisposition;
281 use crate::write::WritePrecondition;
282
283 #[test]
284 fn option_accessors_are_executed_at_runtime() {
285 let disposition: fn(&WriteOptions) -> WriteDisposition = black_box(WriteOptions::disposition);
286 let precondition: for<'a> fn(&'a WriteOptions) -> &'a WritePrecondition = black_box(WriteOptions::precondition);
287 let create_parent: fn(&WriteOptions) -> bool = black_box(WriteOptions::create_parent);
288 let user_metadata: fn(&WriteOptions) -> &NonSensitiveMetadata = black_box(WriteOptions::user_metadata);
289 let checksum: for<'a> fn(&'a WriteOptions) -> Option<&'a Checksum> = black_box(WriteOptions::checksum);
290
291 let options = WriteOptions::default()
292 .with_create_parent(true)
293 .with_disposition(WriteDisposition::CreateOrReplace)
294 .with_checksum(Some(Checksum::new(ChecksumAlgorithm::Sha256, "abc")));
295
296 assert_eq!(WriteDisposition::CreateOrReplace, disposition(&options));
297 assert!(matches!(precondition(&options), WritePrecondition::None));
298 assert!(create_parent(&options));
299 assert!(user_metadata(&options).is_empty());
300 assert!(checksum(&options).is_some());
301 }
302}