use crate::error::FsError;
use crate::error::FsErrorKind;
use crate::error::FsOperation;
use crate::metadata::AtomicityRequirement;
use crate::metadata::Checksum;
use crate::metadata::DurabilityRequirement;
use crate::metadata::FileSystemCapabilities;
use crate::metadata::FileSystemCapability;
use crate::metadata::NonSensitiveMetadata;
use crate::metadata::UserMetadata;
use crate::write::WriteDisposition;
use crate::write::WritePrecondition;
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq)]
pub struct WriteOptions {
create_parent: bool,
disposition: WriteDisposition,
atomicity: AtomicityRequirement,
durability: DurabilityRequirement,
precondition: WritePrecondition,
content_type: Option<String>,
user_metadata: NonSensitiveMetadata,
checksum: Option<Checksum>,
}
impl Default for WriteOptions {
#[inline]
fn default() -> Self {
Self {
create_parent: false,
disposition: WriteDisposition::default(),
atomicity: AtomicityRequirement::default(),
durability: DurabilityRequirement::default(),
precondition: WritePrecondition::default(),
content_type: None,
user_metadata: NonSensitiveMetadata::new(),
checksum: None,
}
}
}
impl WriteOptions {
#[inline]
#[must_use]
pub const fn with_create_parent(mut self, create: bool) -> Self {
self.create_parent = create;
self
}
#[inline]
#[must_use]
pub const fn create_parent(&self) -> bool {
self.create_parent
}
#[inline]
#[must_use]
pub const fn with_disposition(mut self, disposition: WriteDisposition) -> Self {
self.disposition = disposition;
self
}
#[inline]
#[must_use]
pub const fn disposition(&self) -> WriteDisposition {
self.disposition
}
#[inline]
#[must_use]
pub const fn with_atomicity(mut self, atomicity: AtomicityRequirement) -> Self {
self.atomicity = atomicity;
self
}
#[inline]
#[must_use]
pub const fn atomicity(&self) -> AtomicityRequirement {
self.atomicity
}
#[inline]
#[must_use]
pub const fn with_durability(mut self, durability: DurabilityRequirement) -> Self {
self.durability = durability;
self
}
#[inline]
#[must_use]
pub const fn durability(&self) -> DurabilityRequirement {
self.durability
}
#[inline]
#[must_use]
pub fn with_precondition(mut self, precondition: WritePrecondition) -> Self {
self.precondition = precondition;
self
}
#[inline]
#[must_use]
pub const fn precondition(&self) -> &WritePrecondition {
&self.precondition
}
#[inline]
#[must_use]
pub fn with_content_type(mut self, content_type: Option<String>) -> Self {
self.content_type = content_type;
self
}
#[inline]
#[must_use]
pub fn content_type(&self) -> Option<&str> {
self.content_type.as_deref()
}
#[inline]
#[must_use]
pub const fn user_metadata(&self) -> &NonSensitiveMetadata {
&self.user_metadata
}
#[inline]
#[must_use]
pub fn with_checksum(mut self, checksum: Option<Checksum>) -> Self {
self.checksum = checksum;
self
}
#[inline]
#[must_use]
pub const fn checksum(&self) -> Option<&Checksum> {
self.checksum.as_ref()
}
#[inline]
#[must_use]
pub fn with_user_metadata(mut self, metadata: UserMetadata) -> Self {
self.user_metadata = NonSensitiveMetadata::from(metadata);
self
}
#[inline]
pub fn validate(&self) -> Result<(), FsError> {
if self.disposition == WriteDisposition::Append
&& (self.atomicity == AtomicityRequirement::Required || self.precondition != WritePrecondition::None)
{
return Err(FsError::new(
FsErrorKind::InvalidOptions,
FsOperation::OpenWriter,
"append cannot require atomic publication or a destination version",
));
}
if self.disposition == WriteDisposition::CreateNew
&& matches!(&self.precondition, WritePrecondition::IfMatch(_))
{
return Err(FsError::new(
FsErrorKind::InvalidOptions,
FsOperation::OpenWriter,
"create-new cannot require an existing destination version",
));
}
Ok(())
}
pub fn validate_against(&self, capabilities: FileSystemCapabilities) -> Result<(), FsError> {
self.validate()?;
if self.disposition == WriteDisposition::Append && !capabilities.supports(FileSystemCapability::Append) {
return Err(missing_requirement(
FileSystemCapability::Append,
"append writes are required but not supported",
));
}
if self.precondition != WritePrecondition::None
&& !capabilities.supports(FileSystemCapability::ConditionalWrite)
{
return Err(missing_requirement(
FileSystemCapability::ConditionalWrite,
"conditional writes are required but not supported",
));
}
if self.atomicity == AtomicityRequirement::Required
&& !capabilities.supports(FileSystemCapability::AtomicReplace)
{
return Err(missing_requirement(
FileSystemCapability::AtomicReplace,
"atomic write publication is required but not supported",
));
}
if self.durability == DurabilityRequirement::Required
&& !capabilities.supports(FileSystemCapability::DurableWrite)
{
return Err(missing_requirement(
FileSystemCapability::DurableWrite,
"durable write publication is required but not supported",
));
}
Ok(())
}
}
fn missing_requirement(capability: FileSystemCapability, message: &str) -> FsError {
FsError::new(FsErrorKind::RequirementNotMet, FsOperation::OpenWriter, message).with_required_capability(capability)
}
#[cfg(test)]
mod tests {
use std::hint::black_box;
use super::WriteOptions;
use crate::metadata::Checksum;
use crate::metadata::ChecksumAlgorithm;
use crate::metadata::NonSensitiveMetadata;
use crate::write::WriteDisposition;
use crate::write::WritePrecondition;
#[test]
fn option_accessors_are_executed_at_runtime() {
let disposition: fn(&WriteOptions) -> WriteDisposition = black_box(WriteOptions::disposition);
let precondition: for<'a> fn(&'a WriteOptions) -> &'a WritePrecondition = black_box(WriteOptions::precondition);
let create_parent: fn(&WriteOptions) -> bool = black_box(WriteOptions::create_parent);
let user_metadata: fn(&WriteOptions) -> &NonSensitiveMetadata = black_box(WriteOptions::user_metadata);
let checksum: for<'a> fn(&'a WriteOptions) -> Option<&'a Checksum> = black_box(WriteOptions::checksum);
let options = WriteOptions::default()
.with_create_parent(true)
.with_disposition(WriteDisposition::CreateOrReplace)
.with_checksum(Some(Checksum::new(ChecksumAlgorithm::Sha256, "abc")));
assert_eq!(WriteDisposition::CreateOrReplace, disposition(&options));
assert!(matches!(precondition(&options), WritePrecondition::None));
assert!(create_parent(&options));
assert!(user_metadata(&options).is_empty());
assert!(checksum(&options).is_some());
}
}