use crate::error::FsError;
use crate::error::FsErrorKind;
use crate::error::FsOperation;
use crate::metadata::FileSystemCapabilities;
use crate::metadata::FileSystemCapability;
use crate::metadata::ResourceVersion;
use crate::read::ChecksumPolicy;
#[non_exhaustive]
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct ReadOptions {
offset: Option<u64>,
length: Option<u64>,
if_match: Option<ResourceVersion>,
if_none_match: Option<ResourceVersion>,
checksum: ChecksumPolicy,
}
impl ReadOptions {
#[inline]
#[must_use]
pub const fn with_offset(mut self, offset: Option<u64>) -> Self {
self.offset = offset;
self
}
#[inline]
#[must_use]
pub const fn offset(&self) -> Option<u64> {
self.offset
}
#[inline]
#[must_use]
pub const fn with_length(mut self, length: Option<u64>) -> Self {
self.length = length;
self
}
#[inline]
#[must_use]
pub const fn length(&self) -> Option<u64> {
self.length
}
#[inline]
#[must_use]
pub fn with_if_match(mut self, if_match: Option<ResourceVersion>) -> Self {
self.if_match = if_match;
self
}
#[inline]
#[must_use]
pub const fn if_match(&self) -> Option<&ResourceVersion> {
self.if_match.as_ref()
}
#[inline]
#[must_use]
pub fn with_if_none_match(mut self, if_none_match: Option<ResourceVersion>) -> Self {
self.if_none_match = if_none_match;
self
}
#[inline]
#[must_use]
pub const fn if_none_match(&self) -> Option<&ResourceVersion> {
self.if_none_match.as_ref()
}
#[inline]
#[must_use]
pub const fn with_checksum(mut self, checksum: ChecksumPolicy) -> Self {
self.checksum = checksum;
self
}
#[inline]
#[must_use]
pub const fn checksum(&self) -> ChecksumPolicy {
self.checksum
}
pub fn validate(&self) -> Result<(), FsError> {
if self.if_match.is_some() && self.if_none_match.is_some() {
return Err(FsError::new(
FsErrorKind::InvalidOptions,
FsOperation::OpenReader,
"if_match and if_none_match cannot both be specified",
));
}
if let Some(length) = self.length
&& self.offset.unwrap_or(0).checked_add(length).is_none()
{
return Err(FsError::new(
FsErrorKind::InvalidOptions,
FsOperation::OpenReader,
"read range exceeds the representable byte offset",
));
}
Ok(())
}
pub fn validate_against(&self, capabilities: FileSystemCapabilities) -> Result<(), FsError> {
self.validate()?;
if (self.offset.is_some() || self.length.is_some()) && !capabilities.supports(FileSystemCapability::RangeRead) {
return Err(missing_requirement(
FileSystemCapability::RangeRead,
"byte-range reads are required but not supported",
));
}
if (self.if_match.is_some() || self.if_none_match.is_some())
&& !capabilities.supports(FileSystemCapability::ConditionalRead)
{
return Err(missing_requirement(
FileSystemCapability::ConditionalRead,
"conditional reads are required but not supported",
));
}
if self.checksum == ChecksumPolicy::Required && !capabilities.supports(FileSystemCapability::ChecksumValidation)
{
return Err(missing_requirement(
FileSystemCapability::ChecksumValidation,
"checksum validation is required but not supported",
));
}
Ok(())
}
}
fn missing_requirement(capability: FileSystemCapability, message: &str) -> FsError {
FsError::new(FsErrorKind::RequirementNotMet, FsOperation::OpenReader, message).with_required_capability(capability)
}
#[cfg(test)]
mod tests {
use std::hint::black_box;
use super::ReadOptions;
use crate::metadata::ResourceVersion;
#[test]
fn version_accessor_is_executed_at_runtime() {
let if_match: for<'a> fn(&'a ReadOptions) -> Option<&'a ResourceVersion> = black_box(ReadOptions::if_match);
let options = ReadOptions::default().with_if_match(Some(ResourceVersion::new("v1")));
assert_eq!(Some("v1"), if_match(&options).map(ResourceVersion::as_str));
}
}