#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub struct RepositoryCapabilities {
blob_metadata: BlobMetadataCapabilities,
}
impl RepositoryCapabilities {
pub const NONE: Self = Self::new();
pub const fn new() -> Self {
Self {
blob_metadata: BlobMetadataCapabilities::NONE,
}
}
pub const fn blob_metadata(self) -> BlobMetadataCapabilities {
self.blob_metadata
}
pub const fn with_blob_metadata(mut self, capabilities: BlobMetadataCapabilities) -> Self {
self.blob_metadata = capabilities;
self
}
}
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub struct BlobMetadataCapabilities(u8);
impl BlobMetadataCapabilities {
const CREATED: u8 = 1 << 0;
const UPDATED: u8 = 1 << 1;
const ACCESSED: u8 = 1 << 2;
const EXPIRES: u8 = 1 << 3;
const MEDIA_TYPE: u8 = 1 << 4;
pub const NONE: Self = Self(0);
pub const ALL: Self =
Self(Self::CREATED | Self::UPDATED | Self::ACCESSED | Self::EXPIRES | Self::MEDIA_TYPE);
pub const fn new() -> Self {
Self::NONE
}
pub const fn created(self) -> bool {
self.contains(Self::CREATED)
}
pub const fn updated(self) -> bool {
self.contains(Self::UPDATED)
}
pub const fn accessed(self) -> bool {
self.contains(Self::ACCESSED)
}
pub const fn expires(self) -> bool {
self.contains(Self::EXPIRES)
}
pub const fn media_type(self) -> bool {
self.contains(Self::MEDIA_TYPE)
}
pub const fn with_created(mut self) -> Self {
self.0 |= Self::CREATED;
self
}
pub const fn with_updated(mut self) -> Self {
self.0 |= Self::UPDATED;
self
}
pub const fn with_accessed(mut self) -> Self {
self.0 |= Self::ACCESSED;
self
}
pub const fn with_expires(mut self) -> Self {
self.0 |= Self::EXPIRES;
self
}
pub const fn with_media_type(mut self) -> Self {
self.0 |= Self::MEDIA_TYPE;
self
}
const fn contains(self, capability: u8) -> bool {
self.0 & capability != 0
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Blob, Bytes, Id, PutOptions, Repository};
use core::{convert::Infallible, time::Duration};
use futures_util::FutureExt;
struct UnsupportedMetadataRepository;
impl Repository for UnsupportedMetadataRepository {
type Error = Infallible;
async fn get(&self, _id: &Id) -> Result<Option<Blob>, Self::Error> {
Ok(None)
}
async fn put(&mut self, data: Bytes) -> Result<Id, Self::Error> {
Ok(Id::of(data))
}
async fn set_expiry(
&mut self,
_id: &Id,
_expires_nanos: Option<u64>,
) -> Result<bool, Self::Error> {
panic!("unsupported expiry setter must not be called")
}
async fn set_media_type(
&mut self,
_id: &Id,
_media_type: Option<&str>,
) -> Result<bool, Self::Error> {
panic!("unsupported media-type setter must not be called")
}
async fn remove(&mut self, _id: &Id) -> Result<bool, Self::Error> {
Ok(false)
}
async fn clear(&mut self) -> Result<(), Self::Error> {
Ok(())
}
}
#[test]
fn default_put_skips_unsupported_metadata_operations() {
let mut repository = UnsupportedMetadataRepository;
let options = PutOptions::new()
.with_ttl(Duration::from_secs(60))
.with_media_type(Some("text/plain".into()));
let result = repository
.put_with_options(Bytes::from_static(b"capabilities"), options)
.now_or_never()
.expect("the test repository future is immediately ready");
assert!(result.is_ok());
}
struct SupportedMetadataRepository {
expiry_set: bool,
media_type_set: bool,
}
impl Repository for SupportedMetadataRepository {
type Error = Infallible;
fn capabilities(&self) -> RepositoryCapabilities {
RepositoryCapabilities::new().with_blob_metadata(
BlobMetadataCapabilities::new()
.with_expires()
.with_media_type(),
)
}
async fn get(&self, _id: &Id) -> Result<Option<Blob>, Self::Error> {
Ok(None)
}
async fn put(&mut self, data: Bytes) -> Result<Id, Self::Error> {
Ok(Id::of(data))
}
async fn set_expiry(
&mut self,
_id: &Id,
_expires_nanos: Option<u64>,
) -> Result<bool, Self::Error> {
self.expiry_set = true;
Ok(true)
}
async fn set_media_type(
&mut self,
_id: &Id,
_media_type: Option<&str>,
) -> Result<bool, Self::Error> {
self.media_type_set = true;
Ok(true)
}
async fn remove(&mut self, _id: &Id) -> Result<bool, Self::Error> {
Ok(false)
}
async fn clear(&mut self) -> Result<(), Self::Error> {
Ok(())
}
}
#[test]
fn default_put_applies_supported_metadata_operations() {
let mut repository = SupportedMetadataRepository {
expiry_set: false,
media_type_set: false,
};
let options = PutOptions::new()
.with_ttl(Duration::from_secs(60))
.with_media_type(Some("text/plain".into()));
let result = repository
.put_with_options(Bytes::from_static(b"capabilities"), options)
.now_or_never()
.expect("the test repository future is immediately ready");
assert!(result.is_ok());
assert!(repository.expiry_set);
assert!(repository.media_type_set);
}
#[test]
fn metadata_capability_queries_reflect_builders() {
let metadata = BlobMetadataCapabilities::new()
.with_created()
.with_updated()
.with_accessed()
.with_expires()
.with_media_type();
let capabilities = RepositoryCapabilities::new().with_blob_metadata(metadata);
assert!(capabilities.blob_metadata().created());
assert!(capabilities.blob_metadata().updated());
assert!(capabilities.blob_metadata().accessed());
assert!(capabilities.blob_metadata().expires());
assert!(capabilities.blob_metadata().media_type());
}
}