1use std::sync::Arc;
6
7#[derive(Clone)]
9pub struct StateTransform {
10 name: &'static str,
11 transform: Arc<dyn Fn(&mut serde_json::Value) + Send + Sync>,
12}
13
14impl StateTransform {
15 fn new(name: &'static str, f: impl Fn(&mut serde_json::Value) + Send + Sync + 'static) -> Self {
16 Self {
17 name,
18 transform: Arc::new(f),
19 }
20 }
21
22 pub fn apply(&self, state: &mut serde_json::Value) {
24 (self.transform)(state);
25 }
26
27 pub fn name(&self) -> &str {
29 self.name
30 }
31}
32
33impl std::fmt::Debug for StateTransform {
34 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35 f.debug_struct("StateTransform")
36 .field("name", &self.name)
37 .finish()
38 }
39}
40
41impl std::ops::Shr for StateTransform {
43 type Output = StateComposite;
44
45 fn shr(self, rhs: StateTransform) -> Self::Output {
46 StateComposite {
47 steps: vec![self, rhs],
48 }
49 }
50}
51
52#[derive(Clone)]
54#[non_exhaustive]
55pub struct StateComposite {
56 pub steps: Vec<StateTransform>,
58}
59
60impl StateComposite {
61 pub fn apply(&self, state: &mut serde_json::Value) {
63 for step in &self.steps {
64 step.apply(state);
65 }
66 }
67}
68
69impl std::ops::Shr<StateTransform> for StateComposite {
71 type Output = StateComposite;
72
73 fn shr(mut self, rhs: StateTransform) -> Self::Output {
74 self.steps.push(rhs);
75 self
76 }
77}
78
79pub struct S;
81
82impl S {
83 pub fn pick(keys: &[&str]) -> StateTransform {
85 let keys: Vec<String> = keys.iter().map(std::string::ToString::to_string).collect();
86 StateTransform::new("pick", move |state| {
87 if let Some(obj) = state.as_object_mut() {
88 obj.retain(|k, _| keys.contains(k));
89 }
90 })
91 }
92
93 pub fn rename(mappings: &[(&str, &str)]) -> StateTransform {
95 let mappings: Vec<(String, String)> = mappings
96 .iter()
97 .map(|(a, b)| (a.to_string(), b.to_string()))
98 .collect();
99 StateTransform::new("rename", move |state| {
100 if let Some(obj) = state.as_object_mut() {
101 for (from, to) in &mappings {
102 if let Some(val) = obj.remove(from) {
103 obj.insert(to.clone(), val);
104 }
105 }
106 }
107 })
108 }
109
110 pub fn merge(keys: &[&str], into: &str) -> StateTransform {
112 let keys: Vec<String> = keys.iter().map(std::string::ToString::to_string).collect();
113 let into = into.to_string();
114 StateTransform::new("merge", move |state| {
115 if let Some(obj) = state.as_object_mut() {
116 let mut merged = serde_json::Map::new();
117 for key in &keys {
118 if let Some(val) = obj.remove(key) {
119 merged.insert(key.clone(), val);
120 }
121 }
122 obj.insert(into.clone(), serde_json::Value::Object(merged));
123 }
124 })
125 }
126
127 pub fn defaults(defaults: serde_json::Value) -> StateTransform {
129 StateTransform::new("defaults", move |state| {
130 if let (Some(obj), Some(defaults_obj)) = (state.as_object_mut(), defaults.as_object()) {
131 for (k, v) in defaults_obj {
132 obj.entry(k.clone()).or_insert_with(|| v.clone());
133 }
134 }
135 })
136 }
137
138 pub fn map(f: impl Fn(&mut serde_json::Value) + Send + Sync + 'static) -> StateTransform {
140 StateTransform::new("map", f)
141 }
142
143 pub fn flatten(key: &str) -> StateTransform {
145 let key = key.to_string();
146 StateTransform::new("flatten", move |state| {
147 if let Some(obj) = state.as_object_mut()
148 && let Some(serde_json::Value::Object(nested)) = obj.remove(&key)
149 {
150 for (k, v) in nested {
151 obj.insert(k, v);
152 }
153 }
154 })
155 }
156
157 pub fn set(key: &str, value: serde_json::Value) -> StateTransform {
159 let key = key.to_string();
160 StateTransform::new("set", move |state| {
161 if let Some(obj) = state.as_object_mut() {
162 obj.insert(key.clone(), value.clone());
163 }
164 })
165 }
166
167 pub fn is_set(
179 key: &str,
180 ) -> impl Fn(&gemini_adk_rs::State) -> bool + use<> + Send + Sync + 'static {
181 let key = key.to_string();
182 move |s: &gemini_adk_rs::State| s.contains(&key)
183 }
184
185 pub fn is_true(
192 key: &str,
193 ) -> impl Fn(&gemini_adk_rs::State) -> bool + use<> + Send + Sync + 'static {
194 let key = key.to_string();
195 move |s: &gemini_adk_rs::State| s.get::<bool>(&key).unwrap_or(false)
196 }
197
198 pub fn eq(
205 key: &str,
206 expected: &str,
207 ) -> impl Fn(&gemini_adk_rs::State) -> bool + use<> + Send + Sync + 'static {
208 let key = key.to_string();
209 let expected = expected.to_string();
210 move |s: &gemini_adk_rs::State| {
211 s.get::<String>(&key)
212 .map(|v| v == expected)
213 .unwrap_or(false)
214 }
215 }
216
217 pub fn one_of(
227 key: &str,
228 values: &[&str],
229 ) -> impl Fn(&gemini_adk_rs::State) -> bool + use<> + Send + Sync + 'static {
230 let key = key.to_string();
231 let values: Vec<String> = values
232 .iter()
233 .map(std::string::ToString::to_string)
234 .collect();
235 move |s: &gemini_adk_rs::State| s.get::<String>(&key).is_some_and(|v| values.contains(&v))
236 }
237
238 pub fn transform(
240 key: &str,
241 f: impl Fn(serde_json::Value) -> serde_json::Value + Send + Sync + 'static,
242 ) -> StateTransform {
243 let key = key.to_string();
244 StateTransform::new("transform", move |state| {
245 if let Some(obj) = state.as_object_mut()
246 && let Some(val) = obj.remove(&key)
247 {
248 obj.insert(key.clone(), f(val));
249 }
250 })
251 }
252
253 pub fn guard(
255 predicate: impl Fn(&serde_json::Value) -> bool + Send + Sync + 'static,
256 msg: &str,
257 ) -> StateTransform {
258 let msg = msg.to_string();
259 StateTransform::new("guard", move |state| {
260 assert!(predicate(state), "{}", msg);
261 })
262 }
263
264 pub fn compute(
266 key: &str,
267 f: impl Fn(&serde_json::Value) -> serde_json::Value + Send + Sync + 'static,
268 ) -> StateTransform {
269 let key = key.to_string();
270 StateTransform::new("compute", move |state| {
271 let val = f(state);
272 if let Some(obj) = state.as_object_mut() {
273 obj.insert(key.clone(), val);
274 }
275 })
276 }
277
278 pub fn accumulate(source_key: &str, into: &str) -> StateTransform {
280 let source = source_key.to_string();
281 let into = into.to_string();
282 StateTransform::new("accumulate", move |state| {
283 if let Some(obj) = state.as_object_mut()
284 && let Some(val) = obj.get(&source).cloned()
285 {
286 let arr = obj
287 .entry(into.clone())
288 .or_insert_with(|| serde_json::Value::Array(Vec::new()));
289 if let Some(arr) = arr.as_array_mut() {
290 arr.push(val);
291 }
292 }
293 })
294 }
295
296 pub fn counter(key: &str, step: i64) -> StateTransform {
298 let key = key.to_string();
299 StateTransform::new("counter", move |state| {
300 if let Some(obj) = state.as_object_mut() {
301 let current = obj
302 .get(&key)
303 .and_then(serde_json::Value::as_i64)
304 .unwrap_or(0);
305 obj.insert(key.clone(), serde_json::json!(current + step));
306 }
307 })
308 }
309
310 pub fn require(keys: &[&str]) -> StateTransform {
312 let keys: Vec<String> = keys.iter().map(std::string::ToString::to_string).collect();
313 StateTransform::new("require", move |state| {
314 if let Some(obj) = state.as_object() {
315 for key in &keys {
316 assert!(
317 obj.contains_key(key),
318 "Required key '{key}' missing from state"
319 );
320 }
321 }
322 })
323 }
324
325 pub fn identity() -> StateTransform {
327 StateTransform::new("identity", |_| {})
328 }
329
330 pub fn when(
332 predicate: impl Fn(&serde_json::Value) -> bool + Send + Sync + 'static,
333 inner: StateTransform,
334 ) -> StateTransform {
335 StateTransform::new("when", move |state| {
336 if predicate(state) {
337 inner.apply(state);
338 }
339 })
340 }
341
342 pub fn drop(keys: &[&str]) -> StateTransform {
344 let keys: Vec<String> = keys.iter().map(std::string::ToString::to_string).collect();
345 StateTransform::new("drop", move |state| {
346 if let Some(obj) = state.as_object_mut() {
347 for key in &keys {
348 obj.remove(key);
349 }
350 }
351 })
352 }
353
354 pub fn log(message: &str) -> StateTransform {
365 let message = message.to_string();
366 StateTransform::new("log", move |_state| {
367 eprintln!("[S::log] {message}");
368 })
369 }
370
371 pub fn unflatten(key: &str) -> StateTransform {
383 let key = key.to_string();
384 StateTransform::new("unflatten", move |state| {
385 if let Some(obj) = state.as_object_mut() {
386 let prefix = format!("{key}.");
387 let dotted: Vec<(String, serde_json::Value)> = obj
388 .keys()
389 .filter(|k| k.starts_with(&prefix))
390 .cloned()
391 .collect::<Vec<_>>()
392 .into_iter()
393 .filter_map(|k| obj.remove(&k).map(|v| (k, v)))
394 .collect();
395
396 if !dotted.is_empty() {
397 let nested = obj
398 .entry(key.clone())
399 .or_insert_with(|| serde_json::Value::Object(serde_json::Map::new()));
400 if let Some(nested_obj) = nested.as_object_mut() {
401 for (k, v) in dotted {
402 let sub_key = k[prefix.len()..].to_string();
403 nested_obj.insert(sub_key, v);
404 }
405 }
406 }
407 }
408 })
409 }
410
411 pub fn zip(keys: &[&str], into: &str) -> StateTransform {
424 let keys: Vec<String> = keys.iter().map(std::string::ToString::to_string).collect();
425 let into = into.to_string();
426 StateTransform::new("zip", move |state| {
427 if let Some(obj) = state.as_object_mut() {
428 let arrays: Vec<&Vec<serde_json::Value>> = keys
429 .iter()
430 .filter_map(|k| obj.get(k).and_then(|v| v.as_array()))
431 .collect();
432
433 if arrays.len() == keys.len() {
434 let min_len = arrays.iter().map(|a| a.len()).min().unwrap_or(0);
435 let mut zipped = Vec::with_capacity(min_len);
436 for i in 0..min_len {
437 let tuple: Vec<serde_json::Value> =
438 arrays.iter().map(|a| a[i].clone()).collect();
439 zipped.push(serde_json::Value::Array(tuple));
440 }
441 obj.insert(into.clone(), serde_json::Value::Array(zipped));
442 }
443 }
444 })
445 }
446
447 pub fn group_by(source: &str, key: &str, into: &str) -> StateTransform {
460 let source = source.to_string();
461 let key = key.to_string();
462 let into = into.to_string();
463 StateTransform::new("group_by", move |state| {
464 if let Some(obj) = state.as_object_mut()
465 && let Some(arr) = obj.get(&source).and_then(|v| v.as_array())
466 {
467 let mut groups: serde_json::Map<String, serde_json::Value> = serde_json::Map::new();
468 for item in arr {
469 let group_key = item
470 .get(&key)
471 .and_then(|v| v.as_str())
472 .unwrap_or("_unknown")
473 .to_string();
474 let group = groups
475 .entry(group_key)
476 .or_insert_with(|| serde_json::Value::Array(Vec::new()));
477 if let Some(arr) = group.as_array_mut() {
478 arr.push(item.clone());
479 }
480 }
481 obj.insert(into.clone(), serde_json::Value::Object(groups));
482 }
483 })
484 }
485
486 pub fn history(key: &str, max: usize) -> StateTransform {
497 let key = key.to_string();
498 StateTransform::new("history", move |state| {
499 if let Some(obj) = state.as_object_mut() {
500 let history_key = format!("{key}_history");
501 if let Some(val) = obj.get(&key).cloned() {
502 let arr = obj
503 .entry(history_key)
504 .or_insert_with(|| serde_json::Value::Array(Vec::new()));
505 if let Some(arr) = arr.as_array_mut() {
506 arr.push(val);
507 while arr.len() > max {
508 arr.remove(0);
509 }
510 }
511 }
512 }
513 })
514 }
515
516 pub fn validate(schema: serde_json::Value) -> StateTransform {
534 StateTransform::new("validate", move |state| {
535 if let Some(obj) = state.as_object() {
536 if let Some(required) = schema.get("required").and_then(|v| v.as_array()) {
538 for req in required {
539 if let Some(key) = req.as_str() {
540 assert!(
541 obj.contains_key(key),
542 "Validation failed: required key '{key}' missing from state"
543 );
544 }
545 }
546 }
547 if let Some(properties) = schema.get("properties").and_then(|v| v.as_object()) {
549 for (key, prop_schema) in properties {
550 if let Some(val) = obj.get(key)
551 && let Some(expected_type) =
552 prop_schema.get("type").and_then(|v| v.as_str())
553 {
554 let actual_ok = match expected_type {
555 "string" => val.is_string(),
556 "number" | "integer" => val.is_number(),
557 "boolean" => val.is_boolean(),
558 "array" => val.is_array(),
559 "object" => val.is_object(),
560 "null" => val.is_null(),
561 _ => true,
562 };
563 assert!(
564 actual_ok,
565 "Validation failed: key '{key}' expected type '{expected_type}', got {val:?}"
566 );
567 }
568 }
569 }
570 }
571 })
572 }
573
574 pub fn branch(
588 predicate: impl Fn(&serde_json::Value) -> bool + Send + Sync + 'static,
589 if_true: StateTransform,
590 if_false: StateTransform,
591 ) -> StateTransform {
592 StateTransform::new("branch", move |state| {
593 if predicate(state) {
594 if_true.apply(state);
595 } else {
596 if_false.apply(state);
597 }
598 })
599 }
600}
601
602#[cfg(test)]
603mod tests {
604 use super::*;
605 use serde_json::json;
606
607 #[test]
608 fn pick_keeps_only_specified_keys() {
609 let mut state = json!({"a": 1, "b": 2, "c": 3});
610 S::pick(&["a", "c"]).apply(&mut state);
611 assert_eq!(state, json!({"a": 1, "c": 3}));
612 }
613
614 #[test]
615 fn rename_renames_keys() {
616 let mut state = json!({"old_name": 42});
617 S::rename(&[("old_name", "new_name")]).apply(&mut state);
618 assert_eq!(state, json!({"new_name": 42}));
619 }
620
621 #[test]
622 fn merge_combines_keys() {
623 let mut state = json!({"x": 1, "y": 2, "z": 3});
624 S::merge(&["x", "y"], "combined").apply(&mut state);
625 assert_eq!(state, json!({"z": 3, "combined": {"x": 1, "y": 2}}));
626 }
627
628 #[test]
629 fn defaults_sets_missing() {
630 let mut state = json!({"existing": "yes"});
631 S::defaults(json!({"existing": "no", "missing": "added"})).apply(&mut state);
632 assert_eq!(state["existing"], "yes");
633 assert_eq!(state["missing"], "added");
634 }
635
636 #[test]
637 fn drop_removes_keys() {
638 let mut state = json!({"keep": 1, "remove": 2});
639 S::drop(&["remove"]).apply(&mut state);
640 assert_eq!(state, json!({"keep": 1}));
641 }
642
643 #[test]
644 fn map_custom_transform() {
645 let mut state = json!({"count": 5});
646 S::map(|s| {
647 if let Some(n) = s.get("count").and_then(serde_json::Value::as_i64) {
648 s["count"] = json!(n * 2);
649 }
650 })
651 .apply(&mut state);
652 assert_eq!(state["count"], 10);
653 }
654
655 #[test]
656 fn chain_with_shr() {
657 let chain = S::pick(&["a", "b"]) >> S::rename(&[("a", "x")]);
658 let mut state = json!({"a": 1, "b": 2, "c": 3});
659 chain.apply(&mut state);
660 assert_eq!(state, json!({"x": 1, "b": 2}));
661 }
662
663 #[test]
664 fn flatten_nested_object() {
665 let mut state = json!({"nested": {"x": 1, "y": 2}, "z": 3});
666 S::flatten("nested").apply(&mut state);
667 assert_eq!(state, json!({"x": 1, "y": 2, "z": 3}));
668 }
669
670 #[test]
671 fn flatten_missing_key_is_noop() {
672 let mut state = json!({"a": 1});
673 S::flatten("nonexistent").apply(&mut state);
674 assert_eq!(state, json!({"a": 1}));
675 }
676
677 #[test]
678 fn set_inserts_value() {
679 let mut state = json!({"a": 1});
680 S::set("b", json!(42)).apply(&mut state);
681 assert_eq!(state, json!({"a": 1, "b": 42}));
682 }
683
684 #[test]
685 fn set_overwrites_existing() {
686 let mut state = json!({"a": 1});
687 S::set("a", json!("replaced")).apply(&mut state);
688 assert_eq!(state, json!({"a": "replaced"}));
689 }
690
691 #[test]
692 fn chain_extends() {
693 let chain = S::pick(&["a"]) >> S::rename(&[("a", "b")]) >> S::defaults(json!({"c": 99}));
694 let mut state = json!({"a": 1, "x": 2});
695 chain.apply(&mut state);
696 assert_eq!(state, json!({"b": 1, "c": 99}));
697 }
698
699 #[test]
700 fn log_is_noop_on_state() {
701 let mut state = json!({"a": 1});
702 S::log("debug message").apply(&mut state);
703 assert_eq!(state, json!({"a": 1}));
704 }
705
706 #[test]
707 fn unflatten_groups_dotted_keys() {
708 let mut state = json!({"addr.city": "NYC", "addr.zip": "10001", "name": "Alice"});
709 S::unflatten("addr").apply(&mut state);
710 assert_eq!(
711 state,
712 json!({"name": "Alice", "addr": {"city": "NYC", "zip": "10001"}})
713 );
714 }
715
716 #[test]
717 fn unflatten_missing_prefix_is_noop() {
718 let mut state = json!({"a": 1});
719 S::unflatten("addr").apply(&mut state);
720 assert_eq!(state, json!({"a": 1}));
721 }
722
723 #[test]
724 fn zip_combines_arrays() {
725 let mut state = json!({"names": ["a", "b", "c"], "scores": [10, 20, 30]});
726 S::zip(&["names", "scores"], "zipped").apply(&mut state);
727 assert_eq!(state["zipped"], json!([["a", 10], ["b", 20], ["c", 30]]));
728 }
729
730 #[test]
731 fn zip_truncates_to_shortest() {
732 let mut state = json!({"a": [1, 2, 3], "b": [10, 20]});
733 S::zip(&["a", "b"], "z").apply(&mut state);
734 assert_eq!(state["z"], json!([[1, 10], [2, 20]]));
735 }
736
737 #[test]
738 fn group_by_groups_elements() {
739 let mut state = json!({
740 "items": [
741 {"type": "fruit", "name": "apple"},
742 {"type": "veg", "name": "carrot"},
743 {"type": "fruit", "name": "banana"}
744 ]
745 });
746 S::group_by("items", "type", "grouped").apply(&mut state);
747 let grouped = &state["grouped"];
748 assert_eq!(grouped["fruit"].as_array().unwrap().len(), 2);
749 assert_eq!(grouped["veg"].as_array().unwrap().len(), 1);
750 }
751
752 #[test]
753 fn history_tracks_values() {
754 let mut state = json!({"score": 10});
755 let t = S::history("score", 3);
756 t.apply(&mut state);
757 state["score"] = json!(20);
758 t.apply(&mut state);
759 state["score"] = json!(30);
760 t.apply(&mut state);
761 state["score"] = json!(40);
762 t.apply(&mut state);
763 assert_eq!(state["score_history"], json!([20, 30, 40]));
765 }
766
767 #[test]
768 fn validate_passes_valid_state() {
769 let mut state = json!({"name": "Alice", "age": 30});
770 S::validate(json!({
771 "required": ["name", "age"],
772 "properties": {
773 "name": {"type": "string"},
774 "age": {"type": "number"}
775 }
776 }))
777 .apply(&mut state);
778 }
780
781 #[test]
782 #[should_panic(expected = "required key 'missing' missing from state")]
783 fn validate_fails_missing_required() {
784 let mut state = json!({"name": "Alice"});
785 S::validate(json!({"required": ["name", "missing"]})).apply(&mut state);
786 }
787
788 #[test]
789 #[should_panic(expected = "expected type 'string'")]
790 fn validate_fails_wrong_type() {
791 let mut state = json!({"name": 42});
792 S::validate(json!({
793 "properties": {"name": {"type": "string"}}
794 }))
795 .apply(&mut state);
796 }
797
798 #[test]
799 fn branch_takes_true_path() {
800 let mut state = json!({"premium": true});
801 S::branch(
802 |s| {
803 s.get("premium")
804 .and_then(serde_json::Value::as_bool)
805 .unwrap_or(false)
806 },
807 S::set("tier", json!("gold")),
808 S::set("tier", json!("basic")),
809 )
810 .apply(&mut state);
811 assert_eq!(state["tier"], "gold");
812 }
813
814 #[test]
815 fn branch_takes_false_path() {
816 let mut state = json!({"premium": false});
817 S::branch(
818 |s| {
819 s.get("premium")
820 .and_then(serde_json::Value::as_bool)
821 .unwrap_or(false)
822 },
823 S::set("tier", json!("gold")),
824 S::set("tier", json!("basic")),
825 )
826 .apply(&mut state);
827 assert_eq!(state["tier"], "basic");
828 }
829}