noether_core/effects/
effect.rs1use serde::{Deserialize, Serialize};
2use std::collections::BTreeSet;
3
4#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
5#[serde(tag = "effect")]
6pub enum Effect {
7 Cost {
8 cents: u64,
9 },
10 Fallible,
11 Llm {
12 model: String,
13 },
14 Network,
15 NonDeterministic,
16 Process,
18 Pure,
19 Unknown,
20}
21
22#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
27#[serde(rename_all = "snake_case")]
28pub enum EffectKind {
29 Cost,
30 Fallible,
31 Llm,
32 Network,
33 NonDeterministic,
34 Process,
35 Pure,
36 Unknown,
37}
38
39impl Effect {
40 pub fn kind(&self) -> EffectKind {
43 match self {
44 Effect::Cost { .. } => EffectKind::Cost,
45 Effect::Fallible => EffectKind::Fallible,
46 Effect::Llm { .. } => EffectKind::Llm,
47 Effect::Network => EffectKind::Network,
48 Effect::NonDeterministic => EffectKind::NonDeterministic,
49 Effect::Process => EffectKind::Process,
50 Effect::Pure => EffectKind::Pure,
51 Effect::Unknown => EffectKind::Unknown,
52 }
53 }
54}
55
56impl std::fmt::Display for EffectKind {
57 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58 let s = match self {
59 EffectKind::Cost => "cost",
60 EffectKind::Fallible => "fallible",
61 EffectKind::Llm => "llm",
62 EffectKind::Network => "network",
63 EffectKind::NonDeterministic => "non-deterministic",
64 EffectKind::Process => "process",
65 EffectKind::Pure => "pure",
66 EffectKind::Unknown => "unknown",
67 };
68 write!(f, "{s}")
69 }
70}
71
72#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
77pub struct EffectSet {
78 effects: BTreeSet<Effect>,
79}
80
81impl EffectSet {
82 pub fn unknown() -> Self {
83 Self {
84 effects: BTreeSet::from([Effect::Unknown]),
85 }
86 }
87
88 pub fn pure() -> Self {
89 Self {
90 effects: BTreeSet::from([Effect::Pure]),
91 }
92 }
93
94 pub fn new(effects: impl IntoIterator<Item = Effect>) -> Self {
95 Self {
96 effects: effects.into_iter().collect(),
97 }
98 }
99
100 pub fn contains(&self, effect: &Effect) -> bool {
101 self.effects.contains(effect)
102 }
103
104 pub fn is_unknown(&self) -> bool {
105 self.effects.contains(&Effect::Unknown)
106 }
107
108 pub fn iter(&self) -> impl Iterator<Item = &Effect> {
109 self.effects.iter()
110 }
111}
112
113impl Default for EffectSet {
114 fn default() -> Self {
115 Self::unknown()
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 #[test]
124 fn default_is_unknown() {
125 let es = EffectSet::default();
126 assert!(es.is_unknown());
127 assert!(es.contains(&Effect::Unknown));
128 }
129
130 #[test]
131 fn pure_does_not_contain_unknown() {
132 let es = EffectSet::pure();
133 assert!(!es.is_unknown());
134 assert!(es.contains(&Effect::Pure));
135 }
136
137 #[test]
138 fn serde_round_trip() {
139 let es = EffectSet::new([
140 Effect::Network,
141 Effect::Fallible,
142 Effect::Llm {
143 model: "claude-sonnet-4".into(),
144 },
145 ]);
146 let json = serde_json::to_string(&es).unwrap();
147 let deserialized: EffectSet = serde_json::from_str(&json).unwrap();
148 assert_eq!(es, deserialized);
149 }
150}