qubit_fs/read/
read_options.rs1use crate::error::FsError;
11use crate::error::FsErrorKind;
12use crate::error::FsOperation;
13use crate::metadata::FileSystemCapabilities;
14use crate::metadata::FileSystemCapability;
15use crate::metadata::ResourceVersion;
16use crate::read::ChecksumPolicy;
17
18#[non_exhaustive]
30#[derive(Clone, Debug, Default, Eq, PartialEq)]
31pub struct ReadOptions {
32 offset: Option<u64>,
34 length: Option<u64>,
36 if_match: Option<ResourceVersion>,
38 if_none_match: Option<ResourceVersion>,
40 checksum: ChecksumPolicy,
42}
43
44impl ReadOptions {
45 #[inline]
47 #[must_use]
48 pub const fn with_offset(mut self, offset: Option<u64>) -> Self {
49 self.offset = offset;
50 self
51 }
52
53 #[inline]
55 #[must_use]
56 pub const fn offset(&self) -> Option<u64> {
57 self.offset
58 }
59
60 #[inline]
62 #[must_use]
63 pub const fn with_length(mut self, length: Option<u64>) -> Self {
64 self.length = length;
65 self
66 }
67
68 #[inline]
70 #[must_use]
71 pub const fn length(&self) -> Option<u64> {
72 self.length
73 }
74
75 #[inline]
77 #[must_use]
78 pub fn with_if_match(mut self, if_match: Option<ResourceVersion>) -> Self {
79 self.if_match = if_match;
80 self
81 }
82
83 #[inline]
85 #[must_use]
86 pub const fn if_match(&self) -> Option<&ResourceVersion> {
87 self.if_match.as_ref()
88 }
89
90 #[inline]
92 #[must_use]
93 pub fn with_if_none_match(mut self, if_none_match: Option<ResourceVersion>) -> Self {
94 self.if_none_match = if_none_match;
95 self
96 }
97
98 #[inline]
100 #[must_use]
101 pub const fn if_none_match(&self) -> Option<&ResourceVersion> {
102 self.if_none_match.as_ref()
103 }
104
105 #[inline]
107 #[must_use]
108 pub const fn with_checksum(mut self, checksum: ChecksumPolicy) -> Self {
109 self.checksum = checksum;
110 self
111 }
112
113 #[inline]
115 #[must_use]
116 pub const fn checksum(&self) -> ChecksumPolicy {
117 self.checksum
118 }
119
120 pub fn validate(&self) -> Result<(), FsError> {
131 if self.if_match.is_some() && self.if_none_match.is_some() {
132 return Err(FsError::new(
133 FsErrorKind::InvalidOptions,
134 FsOperation::OpenReader,
135 "if_match and if_none_match cannot both be specified",
136 ));
137 }
138 if let Some(length) = self.length
139 && self.offset.unwrap_or(0).checked_add(length).is_none()
140 {
141 return Err(FsError::new(
142 FsErrorKind::InvalidOptions,
143 FsOperation::OpenReader,
144 "read range exceeds the representable byte offset",
145 ));
146 }
147 Ok(())
148 }
149
150 pub fn validate_against(&self, capabilities: FileSystemCapabilities) -> Result<(), FsError> {
161 self.validate()?;
162 if (self.offset.is_some() || self.length.is_some()) && !capabilities.supports(FileSystemCapability::RangeRead) {
163 return Err(missing_requirement(
164 FileSystemCapability::RangeRead,
165 "byte-range reads are required but not supported",
166 ));
167 }
168 if (self.if_match.is_some() || self.if_none_match.is_some())
169 && !capabilities.supports(FileSystemCapability::ConditionalRead)
170 {
171 return Err(missing_requirement(
172 FileSystemCapability::ConditionalRead,
173 "conditional reads are required but not supported",
174 ));
175 }
176 if self.checksum == ChecksumPolicy::Required && !capabilities.supports(FileSystemCapability::ChecksumValidation)
177 {
178 return Err(missing_requirement(
179 FileSystemCapability::ChecksumValidation,
180 "checksum validation is required but not supported",
181 ));
182 }
183 Ok(())
184 }
185}
186
187fn missing_requirement(capability: FileSystemCapability, message: &str) -> FsError {
189 FsError::new(FsErrorKind::RequirementNotMet, FsOperation::OpenReader, message).with_required_capability(capability)
190}
191
192#[cfg(test)]
193mod tests {
194 use std::hint::black_box;
195
196 use super::ReadOptions;
197 use crate::metadata::ResourceVersion;
198
199 #[test]
200 fn version_accessor_is_executed_at_runtime() {
201 let if_match: for<'a> fn(&'a ReadOptions) -> Option<&'a ResourceVersion> = black_box(ReadOptions::if_match);
202 let options = ReadOptions::default().with_if_match(Some(ResourceVersion::new("v1")));
203
204 assert_eq!(Some("v1"), if_match(&options).map(ResourceVersion::as_str));
205 }
206}