use serde::{Deserialize, Serialize};
use std::collections::BTreeSet;
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(tag = "effect")]
pub enum Effect {
Cost {
cents: u64,
},
Fallible,
Llm {
model: String,
},
Network,
NonDeterministic,
Process,
Pure,
Unknown,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EffectKind {
Cost,
Fallible,
Llm,
Network,
NonDeterministic,
Process,
Pure,
Unknown,
}
impl Effect {
pub fn kind(&self) -> EffectKind {
match self {
Effect::Cost { .. } => EffectKind::Cost,
Effect::Fallible => EffectKind::Fallible,
Effect::Llm { .. } => EffectKind::Llm,
Effect::Network => EffectKind::Network,
Effect::NonDeterministic => EffectKind::NonDeterministic,
Effect::Process => EffectKind::Process,
Effect::Pure => EffectKind::Pure,
Effect::Unknown => EffectKind::Unknown,
}
}
}
impl std::fmt::Display for EffectKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
EffectKind::Cost => "cost",
EffectKind::Fallible => "fallible",
EffectKind::Llm => "llm",
EffectKind::Network => "network",
EffectKind::NonDeterministic => "non-deterministic",
EffectKind::Process => "process",
EffectKind::Pure => "pure",
EffectKind::Unknown => "unknown",
};
write!(f, "{s}")
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct EffectSet {
effects: BTreeSet<Effect>,
}
impl EffectSet {
pub fn unknown() -> Self {
Self {
effects: BTreeSet::from([Effect::Unknown]),
}
}
pub fn pure() -> Self {
Self {
effects: BTreeSet::from([Effect::Pure]),
}
}
pub fn new(effects: impl IntoIterator<Item = Effect>) -> Self {
Self {
effects: effects.into_iter().collect(),
}
}
pub fn contains(&self, effect: &Effect) -> bool {
self.effects.contains(effect)
}
pub fn is_unknown(&self) -> bool {
self.effects.contains(&Effect::Unknown)
}
pub fn iter(&self) -> impl Iterator<Item = &Effect> {
self.effects.iter()
}
}
impl Default for EffectSet {
fn default() -> Self {
Self::unknown()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_is_unknown() {
let es = EffectSet::default();
assert!(es.is_unknown());
assert!(es.contains(&Effect::Unknown));
}
#[test]
fn pure_does_not_contain_unknown() {
let es = EffectSet::pure();
assert!(!es.is_unknown());
assert!(es.contains(&Effect::Pure));
}
#[test]
fn serde_round_trip() {
let es = EffectSet::new([
Effect::Network,
Effect::Fallible,
Effect::Llm {
model: "claude-sonnet-4".into(),
},
]);
let json = serde_json::to_string(&es).unwrap();
let deserialized: EffectSet = serde_json::from_str(&json).unwrap();
assert_eq!(es, deserialized);
}
}