use crate::error::FactoryError;
use crate::options::EntryOptions;
use crate::tags::Tag;
use crate::time::Timestamp;
#[derive(Debug)]
pub struct FactoryProduct<V> {
pub(crate) value: V,
pub(crate) options: EntryOptions,
pub(crate) etag: Option<String>,
pub(crate) last_modified: Option<Timestamp>,
pub(crate) tags: Box<[Tag]>,
pub(crate) reused_stale: bool,
}
impl<V> FactoryProduct<V> {
#[must_use]
pub fn value(&self) -> &V {
&self.value
}
}
#[derive(Debug)]
pub struct FactoryContext<V> {
key: std::sync::Arc<str>,
options: EntryOptions,
call_tags: Box<[Tag]>,
adaptive_tags: Option<Box<[Tag]>>,
stale_value: Option<V>,
stale_etag: Option<String>,
stale_last_modified: Option<Timestamp>,
stale_tags: Box<[Tag]>,
}
impl<V> FactoryContext<V> {
pub(crate) fn new(
key: std::sync::Arc<str>,
options: EntryOptions,
call_tags: Box<[Tag]>,
stale: Option<StaleInfo<V>>,
) -> Self {
let (stale_value, stale_etag, stale_last_modified, stale_tags) = match stale {
Some(s) => (Some(s.value), s.etag, s.last_modified, s.tags),
None => (None, None, None, Box::from([])),
};
Self {
key,
options,
call_tags,
adaptive_tags: None,
stale_value,
stale_etag,
stale_last_modified,
stale_tags,
}
}
#[must_use]
pub fn key(&self) -> &str {
&self.key
}
#[must_use]
pub fn options_mut(&mut self) -> &mut EntryOptions {
&mut self.options
}
#[must_use]
pub fn options(&self) -> &EntryOptions {
&self.options
}
pub fn adapt<F>(&mut self, f: F)
where
F: FnOnce(EntryOptions) -> EntryOptions,
{
let current = self.options.clone();
self.options = f(current);
}
#[must_use]
pub fn has_stale_value(&self) -> bool {
self.stale_value.is_some()
}
#[must_use]
pub fn stale_value(&self) -> Option<&V> {
self.stale_value.as_ref()
}
#[must_use]
pub fn stale_etag(&self) -> Option<&str> {
self.stale_etag.as_deref()
}
#[must_use]
pub fn stale_last_modified(&self) -> Option<Timestamp> {
self.stale_last_modified
}
pub fn set_tags<I, S>(&mut self, tags: I)
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
self.adaptive_tags = Some(crate::tags::collect_tags(tags));
}
fn effective_tags(&self) -> Box<[Tag]> {
self.adaptive_tags
.clone()
.unwrap_or_else(|| self.call_tags.clone())
}
#[must_use]
pub fn value(self, value: V) -> FactoryProduct<V> {
let tags = self.effective_tags();
FactoryProduct {
value,
options: self.options,
etag: None,
last_modified: None,
tags,
reused_stale: false,
}
}
pub fn modified(self, value: V) -> ModifiedBuilder<V> {
ModifiedBuilder {
ctx: self,
value,
etag: None,
last_modified: None,
tags: None,
}
}
#[must_use]
pub fn fail(&self, message: impl Into<String>) -> FactoryError {
FactoryError::new(message)
}
}
impl<V: Clone> FactoryContext<V> {
pub fn not_modified(self) -> Result<FactoryProduct<V>, FactoryError> {
match &self.stale_value {
Some(value) => Ok(FactoryProduct {
value: value.clone(),
options: self.options.clone(),
etag: self.stale_etag.clone(),
last_modified: self.stale_last_modified,
tags: self.stale_tags.clone(),
reused_stale: true,
}),
None => Err(FactoryError::new(
"not_modified() was called but no stale value is available to reuse",
)),
}
}
}
pub(crate) struct StaleInfo<V> {
pub(crate) value: V,
pub(crate) etag: Option<String>,
pub(crate) last_modified: Option<Timestamp>,
pub(crate) tags: Box<[Tag]>,
}
#[derive(Debug)]
#[must_use = "call `.done()` to produce the FactoryProduct"]
pub struct ModifiedBuilder<V> {
ctx: FactoryContext<V>,
value: V,
etag: Option<String>,
last_modified: Option<Timestamp>,
tags: Option<Box<[Tag]>>,
}
impl<V> ModifiedBuilder<V> {
pub fn etag(mut self, etag: impl Into<String>) -> Self {
self.etag = Some(etag.into());
self
}
pub fn last_modified(mut self, last_modified: Timestamp) -> Self {
self.last_modified = Some(last_modified);
self
}
pub fn tags<I, S>(mut self, tags: I) -> Self
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
self.tags = Some(crate::tags::collect_tags(tags));
self
}
#[must_use]
pub fn done(self) -> FactoryProduct<V> {
let tags = self.tags.unwrap_or_else(|| self.ctx.effective_tags());
FactoryProduct {
value: self.value,
options: self.ctx.options,
etag: self.etag,
last_modified: self.last_modified,
tags,
reused_stale: false,
}
}
}