#![deny(missing_docs)]
use std::collections::HashSet;
use std::fmt;
use std::str::FromStr;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
pub enum SideEffect {
Read,
Write,
Idempotent,
Destructive,
External,
Expensive,
Network,
}
impl SideEffect {
pub fn as_str(&self) -> &'static str {
match self {
SideEffect::Read => "read",
SideEffect::Write => "write",
SideEffect::Idempotent => "idempotent",
SideEffect::Destructive => "destructive",
SideEffect::External => "external",
SideEffect::Expensive => "expensive",
SideEffect::Network => "network",
}
}
}
impl fmt::Display for SideEffect {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseSideEffectError {
pub input: String,
}
impl fmt::Display for ParseSideEffectError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "unknown side-effect tag: {:?}", self.input)
}
}
impl std::error::Error for ParseSideEffectError {}
impl FromStr for SideEffect {
type Err = ParseSideEffectError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"read" => Ok(SideEffect::Read),
"write" => Ok(SideEffect::Write),
"idempotent" => Ok(SideEffect::Idempotent),
"destructive" => Ok(SideEffect::Destructive),
"external" => Ok(SideEffect::External),
"expensive" => Ok(SideEffect::Expensive),
"network" => Ok(SideEffect::Network),
other => Err(ParseSideEffectError {
input: other.to_string(),
}),
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
pub struct SideEffects {
inner: HashSet<SideEffect>,
}
impl SideEffects {
pub fn new() -> Self {
Self {
inner: HashSet::new(),
}
}
pub fn insert(&mut self, effect: SideEffect) -> bool {
self.inner.insert(effect)
}
pub fn remove(&mut self, effect: SideEffect) -> bool {
self.inner.remove(&effect)
}
pub fn contains(&self, effect: SideEffect) -> bool {
self.inner.contains(&effect)
}
pub fn iter(&self) -> impl Iterator<Item = &SideEffect> {
self.inner.iter()
}
pub fn len(&self) -> usize {
self.inner.len()
}
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
}
impl FromIterator<SideEffect> for SideEffects {
fn from_iter<I: IntoIterator<Item = SideEffect>>(iter: I) -> Self {
Self {
inner: iter.into_iter().collect(),
}
}
}
pub trait HasSideEffects {
fn side_effects(&self) -> SideEffects;
}
#[derive(Debug, Clone)]
pub struct Tag<T> {
value: T,
effects: SideEffects,
}
impl<T> Tag<T> {
pub fn new(value: T, effects: SideEffects) -> Self {
Self { value, effects }
}
pub fn value(&self) -> &T {
&self.value
}
pub fn effects(&self) -> &SideEffects {
&self.effects
}
pub fn into_inner(self) -> T {
self.value
}
}
impl<T> HasSideEffects for Tag<T> {
fn side_effects(&self) -> SideEffects {
self.effects.clone()
}
}
pub fn is_parallel_safe(effects: &SideEffects) -> bool {
if effects.is_empty() {
return false;
}
if effects.contains(SideEffect::Write) || effects.contains(SideEffect::Destructive) {
return false;
}
effects.contains(SideEffect::Read)
}
pub fn is_retry_safe(effects: &SideEffects) -> bool {
if effects.is_empty() {
return false;
}
if effects.contains(SideEffect::Destructive) {
return false;
}
if effects.contains(SideEffect::Idempotent) {
return true;
}
if effects.contains(SideEffect::Read) && !effects.contains(SideEffect::Write) {
return true;
}
false
}
pub fn is_destructive(effects: &SideEffects) -> bool {
effects.contains(SideEffect::Destructive)
}