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 std::fmt::Debug for StateComposite {
61 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62 f.debug_list()
63 .entries(self.steps.iter().map(StateTransform::name))
64 .finish()
65 }
66}
67
68impl From<StateTransform> for StateComposite {
69 fn from(step: StateTransform) -> Self {
70 Self { steps: vec![step] }
71 }
72}
73
74impl StateComposite {
75 pub fn apply(&self, state: &mut serde_json::Value) {
77 for step in &self.steps {
78 step.apply(state);
79 }
80 }
81}
82
83impl std::ops::Shr<StateTransform> for StateComposite {
85 type Output = StateComposite;
86
87 fn shr(mut self, rhs: StateTransform) -> Self::Output {
88 self.steps.push(rhs);
89 self
90 }
91}
92
93pub struct S;
95
96impl S {
97 pub fn pick(keys: &[&str]) -> StateTransform {
99 let keys: Vec<String> = keys.iter().map(std::string::ToString::to_string).collect();
100 StateTransform::new("pick", move |state| {
101 if let Some(obj) = state.as_object_mut() {
102 obj.retain(|k, _| keys.contains(k));
103 }
104 })
105 }
106
107 pub fn rename(mappings: &[(&str, &str)]) -> StateTransform {
109 let mappings: Vec<(String, String)> = mappings
110 .iter()
111 .map(|(a, b)| (a.to_string(), b.to_string()))
112 .collect();
113 StateTransform::new("rename", move |state| {
114 if let Some(obj) = state.as_object_mut() {
115 for (from, to) in &mappings {
116 if let Some(val) = obj.remove(from) {
117 obj.insert(to.clone(), val);
118 }
119 }
120 }
121 })
122 }
123
124 pub fn merge(keys: &[&str], into: &str) -> StateTransform {
126 let keys: Vec<String> = keys.iter().map(std::string::ToString::to_string).collect();
127 let into = into.to_string();
128 StateTransform::new("merge", move |state| {
129 if let Some(obj) = state.as_object_mut() {
130 let mut merged = serde_json::Map::new();
131 for key in &keys {
132 if let Some(val) = obj.remove(key) {
133 merged.insert(key.clone(), val);
134 }
135 }
136 obj.insert(into.clone(), serde_json::Value::Object(merged));
137 }
138 })
139 }
140
141 pub fn defaults(defaults: serde_json::Value) -> StateTransform {
143 StateTransform::new("defaults", move |state| {
144 if let (Some(obj), Some(defaults_obj)) = (state.as_object_mut(), defaults.as_object()) {
145 for (k, v) in defaults_obj {
146 obj.entry(k.clone()).or_insert_with(|| v.clone());
147 }
148 }
149 })
150 }
151
152 pub fn map(f: impl Fn(&mut serde_json::Value) + Send + Sync + 'static) -> StateTransform {
154 StateTransform::new("map", f)
155 }
156
157 pub fn flatten(key: &str) -> StateTransform {
159 let key = key.to_string();
160 StateTransform::new("flatten", move |state| {
161 if let Some(obj) = state.as_object_mut()
162 && let Some(serde_json::Value::Object(nested)) = obj.remove(&key)
163 {
164 for (k, v) in nested {
165 obj.insert(k, v);
166 }
167 }
168 })
169 }
170
171 pub fn set(key: &str, value: serde_json::Value) -> StateTransform {
173 let key = key.to_string();
174 StateTransform::new("set", move |state| {
175 if let Some(obj) = state.as_object_mut() {
176 obj.insert(key.clone(), value.clone());
177 }
178 })
179 }
180
181 pub fn is_set(
193 key: &str,
194 ) -> impl Fn(&gemini_adk_rs::State) -> bool + use<> + Send + Sync + 'static {
195 let key = key.to_string();
196 move |s: &gemini_adk_rs::State| s.contains(&key)
197 }
198
199 pub fn is_true(
206 key: &str,
207 ) -> impl Fn(&gemini_adk_rs::State) -> bool + use<> + Send + Sync + 'static {
208 let key = key.to_string();
209 move |s: &gemini_adk_rs::State| s.get::<bool>(&key).unwrap_or(false)
210 }
211
212 pub fn eq(
219 key: &str,
220 expected: &str,
221 ) -> impl Fn(&gemini_adk_rs::State) -> bool + use<> + Send + Sync + 'static {
222 let key = key.to_string();
223 let expected = expected.to_string();
224 move |s: &gemini_adk_rs::State| {
225 s.get::<String>(&key)
226 .map(|v| v == expected)
227 .unwrap_or(false)
228 }
229 }
230
231 pub fn one_of(
241 key: &str,
242 values: &[&str],
243 ) -> impl Fn(&gemini_adk_rs::State) -> bool + use<> + Send + Sync + 'static {
244 let key = key.to_string();
245 let values: Vec<String> = values
246 .iter()
247 .map(std::string::ToString::to_string)
248 .collect();
249 move |s: &gemini_adk_rs::State| s.get::<String>(&key).is_some_and(|v| values.contains(&v))
250 }
251
252 pub fn transform(
254 key: &str,
255 f: impl Fn(serde_json::Value) -> serde_json::Value + Send + Sync + 'static,
256 ) -> StateTransform {
257 let key = key.to_string();
258 StateTransform::new("transform", move |state| {
259 if let Some(obj) = state.as_object_mut()
260 && let Some(val) = obj.remove(&key)
261 {
262 obj.insert(key.clone(), f(val));
263 }
264 })
265 }
266
267 pub fn guard(
269 predicate: impl Fn(&serde_json::Value) -> bool + Send + Sync + 'static,
270 msg: &str,
271 ) -> StateTransform {
272 let msg = msg.to_string();
273 StateTransform::new("guard", move |state| {
274 assert!(predicate(state), "{}", msg);
275 })
276 }
277
278 pub fn compute(
280 key: &str,
281 f: impl Fn(&serde_json::Value) -> serde_json::Value + Send + Sync + 'static,
282 ) -> StateTransform {
283 let key = key.to_string();
284 StateTransform::new("compute", move |state| {
285 let val = f(state);
286 if let Some(obj) = state.as_object_mut() {
287 obj.insert(key.clone(), val);
288 }
289 })
290 }
291
292 pub fn accumulate(source_key: &str, into: &str) -> StateTransform {
294 let source = source_key.to_string();
295 let into = into.to_string();
296 StateTransform::new("accumulate", move |state| {
297 if let Some(obj) = state.as_object_mut()
298 && let Some(val) = obj.get(&source).cloned()
299 {
300 let arr = obj
301 .entry(into.clone())
302 .or_insert_with(|| serde_json::Value::Array(Vec::new()));
303 if let Some(arr) = arr.as_array_mut() {
304 arr.push(val);
305 }
306 }
307 })
308 }
309
310 pub fn counter(key: &str, step: i64) -> StateTransform {
312 let key = key.to_string();
313 StateTransform::new("counter", move |state| {
314 if let Some(obj) = state.as_object_mut() {
315 let current = obj
316 .get(&key)
317 .and_then(serde_json::Value::as_i64)
318 .unwrap_or(0);
319 obj.insert(key.clone(), serde_json::json!(current + step));
320 }
321 })
322 }
323
324 pub fn require(keys: &[&str]) -> StateTransform {
326 let keys: Vec<String> = keys.iter().map(std::string::ToString::to_string).collect();
327 StateTransform::new("require", move |state| {
328 if let Some(obj) = state.as_object() {
329 for key in &keys {
330 assert!(
331 obj.contains_key(key),
332 "Required key '{key}' missing from state"
333 );
334 }
335 }
336 })
337 }
338
339 pub fn identity() -> StateTransform {
341 StateTransform::new("identity", |_| {})
342 }
343
344 pub fn when(
346 predicate: impl Fn(&serde_json::Value) -> bool + Send + Sync + 'static,
347 inner: StateTransform,
348 ) -> StateTransform {
349 StateTransform::new("when", move |state| {
350 if predicate(state) {
351 inner.apply(state);
352 }
353 })
354 }
355
356 pub fn drop(keys: &[&str]) -> StateTransform {
358 let keys: Vec<String> = keys.iter().map(std::string::ToString::to_string).collect();
359 StateTransform::new("drop", move |state| {
360 if let Some(obj) = state.as_object_mut() {
361 for key in &keys {
362 obj.remove(key);
363 }
364 }
365 })
366 }
367
368 pub fn log(message: &str) -> StateTransform {
379 let message = message.to_string();
380 StateTransform::new("log", move |_state| {
381 eprintln!("[S::log] {message}");
382 })
383 }
384
385 pub fn unflatten(key: &str) -> StateTransform {
397 let key = key.to_string();
398 StateTransform::new("unflatten", move |state| {
399 if let Some(obj) = state.as_object_mut() {
400 let prefix = format!("{key}.");
401 let dotted: Vec<(String, serde_json::Value)> = obj
402 .keys()
403 .filter(|k| k.starts_with(&prefix))
404 .cloned()
405 .collect::<Vec<_>>()
406 .into_iter()
407 .filter_map(|k| obj.remove(&k).map(|v| (k, v)))
408 .collect();
409
410 if !dotted.is_empty() {
411 let nested = obj
412 .entry(key.clone())
413 .or_insert_with(|| serde_json::Value::Object(serde_json::Map::new()));
414 if let Some(nested_obj) = nested.as_object_mut() {
415 for (k, v) in dotted {
416 let sub_key = k[prefix.len()..].to_string();
417 nested_obj.insert(sub_key, v);
418 }
419 }
420 }
421 }
422 })
423 }
424
425 pub fn zip(keys: &[&str], into: &str) -> StateTransform {
438 let keys: Vec<String> = keys.iter().map(std::string::ToString::to_string).collect();
439 let into = into.to_string();
440 StateTransform::new("zip", move |state| {
441 if let Some(obj) = state.as_object_mut() {
442 let arrays: Vec<&Vec<serde_json::Value>> = keys
443 .iter()
444 .filter_map(|k| obj.get(k).and_then(|v| v.as_array()))
445 .collect();
446
447 if arrays.len() == keys.len() {
448 let min_len = arrays.iter().map(|a| a.len()).min().unwrap_or(0);
449 let mut zipped = Vec::with_capacity(min_len);
450 for i in 0..min_len {
451 let tuple: Vec<serde_json::Value> =
452 arrays.iter().map(|a| a[i].clone()).collect();
453 zipped.push(serde_json::Value::Array(tuple));
454 }
455 obj.insert(into.clone(), serde_json::Value::Array(zipped));
456 }
457 }
458 })
459 }
460
461 pub fn group_by(source: &str, key: &str, into: &str) -> StateTransform {
474 let source = source.to_string();
475 let key = key.to_string();
476 let into = into.to_string();
477 StateTransform::new("group_by", move |state| {
478 if let Some(obj) = state.as_object_mut()
479 && let Some(arr) = obj.get(&source).and_then(|v| v.as_array())
480 {
481 let mut groups: serde_json::Map<String, serde_json::Value> = serde_json::Map::new();
482 for item in arr {
483 let group_key = item
484 .get(&key)
485 .and_then(|v| v.as_str())
486 .unwrap_or("_unknown")
487 .to_string();
488 let group = groups
489 .entry(group_key)
490 .or_insert_with(|| serde_json::Value::Array(Vec::new()));
491 if let Some(arr) = group.as_array_mut() {
492 arr.push(item.clone());
493 }
494 }
495 obj.insert(into.clone(), serde_json::Value::Object(groups));
496 }
497 })
498 }
499
500 pub fn history(key: &str, max: usize) -> StateTransform {
511 let key = key.to_string();
512 StateTransform::new("history", move |state| {
513 if let Some(obj) = state.as_object_mut() {
514 let history_key = format!("{key}_history");
515 if let Some(val) = obj.get(&key).cloned() {
516 let arr = obj
517 .entry(history_key)
518 .or_insert_with(|| serde_json::Value::Array(Vec::new()));
519 if let Some(arr) = arr.as_array_mut() {
520 arr.push(val);
521 while arr.len() > max {
522 arr.remove(0);
523 }
524 }
525 }
526 }
527 })
528 }
529
530 pub fn validate(schema: serde_json::Value) -> StateTransform {
548 StateTransform::new("validate", move |state| {
549 if let Some(obj) = state.as_object() {
550 if let Some(required) = schema.get("required").and_then(|v| v.as_array()) {
552 for req in required {
553 if let Some(key) = req.as_str() {
554 assert!(
555 obj.contains_key(key),
556 "Validation failed: required key '{key}' missing from state"
557 );
558 }
559 }
560 }
561 if let Some(properties) = schema.get("properties").and_then(|v| v.as_object()) {
563 for (key, prop_schema) in properties {
564 if let Some(val) = obj.get(key)
565 && let Some(expected_type) =
566 prop_schema.get("type").and_then(|v| v.as_str())
567 {
568 let actual_ok = match expected_type {
569 "string" => val.is_string(),
570 "number" | "integer" => val.is_number(),
571 "boolean" => val.is_boolean(),
572 "array" => val.is_array(),
573 "object" => val.is_object(),
574 "null" => val.is_null(),
575 _ => true,
576 };
577 assert!(
578 actual_ok,
579 "Validation failed: key '{key}' expected type '{expected_type}', got {val:?}"
580 );
581 }
582 }
583 }
584 }
585 })
586 }
587
588 pub fn branch(
602 predicate: impl Fn(&serde_json::Value) -> bool + Send + Sync + 'static,
603 if_true: StateTransform,
604 if_false: StateTransform,
605 ) -> StateTransform {
606 StateTransform::new("branch", move |state| {
607 if predicate(state) {
608 if_true.apply(state);
609 } else {
610 if_false.apply(state);
611 }
612 })
613 }
614}
615
616#[cfg(test)]
617mod tests {
618 use super::*;
619 use serde_json::json;
620
621 #[test]
622 fn pick_keeps_only_specified_keys() {
623 let mut state = json!({"a": 1, "b": 2, "c": 3});
624 S::pick(&["a", "c"]).apply(&mut state);
625 assert_eq!(state, json!({"a": 1, "c": 3}));
626 }
627
628 #[test]
629 fn rename_renames_keys() {
630 let mut state = json!({"old_name": 42});
631 S::rename(&[("old_name", "new_name")]).apply(&mut state);
632 assert_eq!(state, json!({"new_name": 42}));
633 }
634
635 #[test]
636 fn merge_combines_keys() {
637 let mut state = json!({"x": 1, "y": 2, "z": 3});
638 S::merge(&["x", "y"], "combined").apply(&mut state);
639 assert_eq!(state, json!({"z": 3, "combined": {"x": 1, "y": 2}}));
640 }
641
642 #[test]
643 fn defaults_sets_missing() {
644 let mut state = json!({"existing": "yes"});
645 S::defaults(json!({"existing": "no", "missing": "added"})).apply(&mut state);
646 assert_eq!(state["existing"], "yes");
647 assert_eq!(state["missing"], "added");
648 }
649
650 #[test]
651 fn drop_removes_keys() {
652 let mut state = json!({"keep": 1, "remove": 2});
653 S::drop(&["remove"]).apply(&mut state);
654 assert_eq!(state, json!({"keep": 1}));
655 }
656
657 #[test]
658 fn map_custom_transform() {
659 let mut state = json!({"count": 5});
660 S::map(|s| {
661 if let Some(n) = s.get("count").and_then(serde_json::Value::as_i64) {
662 s["count"] = json!(n * 2);
663 }
664 })
665 .apply(&mut state);
666 assert_eq!(state["count"], 10);
667 }
668
669 #[test]
670 fn chain_with_shr() {
671 let chain = S::pick(&["a", "b"]) >> S::rename(&[("a", "x")]);
672 let mut state = json!({"a": 1, "b": 2, "c": 3});
673 chain.apply(&mut state);
674 assert_eq!(state, json!({"x": 1, "b": 2}));
675 }
676
677 #[test]
678 fn flatten_nested_object() {
679 let mut state = json!({"nested": {"x": 1, "y": 2}, "z": 3});
680 S::flatten("nested").apply(&mut state);
681 assert_eq!(state, json!({"x": 1, "y": 2, "z": 3}));
682 }
683
684 #[test]
685 fn flatten_missing_key_is_noop() {
686 let mut state = json!({"a": 1});
687 S::flatten("nonexistent").apply(&mut state);
688 assert_eq!(state, json!({"a": 1}));
689 }
690
691 #[test]
692 fn set_inserts_value() {
693 let mut state = json!({"a": 1});
694 S::set("b", json!(42)).apply(&mut state);
695 assert_eq!(state, json!({"a": 1, "b": 42}));
696 }
697
698 #[test]
699 fn set_overwrites_existing() {
700 let mut state = json!({"a": 1});
701 S::set("a", json!("replaced")).apply(&mut state);
702 assert_eq!(state, json!({"a": "replaced"}));
703 }
704
705 #[test]
706 fn chain_extends() {
707 let chain = S::pick(&["a"]) >> S::rename(&[("a", "b")]) >> S::defaults(json!({"c": 99}));
708 let mut state = json!({"a": 1, "x": 2});
709 chain.apply(&mut state);
710 assert_eq!(state, json!({"b": 1, "c": 99}));
711 }
712
713 #[test]
714 fn log_is_noop_on_state() {
715 let mut state = json!({"a": 1});
716 S::log("debug message").apply(&mut state);
717 assert_eq!(state, json!({"a": 1}));
718 }
719
720 #[test]
721 fn unflatten_groups_dotted_keys() {
722 let mut state = json!({"addr.city": "NYC", "addr.zip": "10001", "name": "Alice"});
723 S::unflatten("addr").apply(&mut state);
724 assert_eq!(
725 state,
726 json!({"name": "Alice", "addr": {"city": "NYC", "zip": "10001"}})
727 );
728 }
729
730 #[test]
731 fn unflatten_missing_prefix_is_noop() {
732 let mut state = json!({"a": 1});
733 S::unflatten("addr").apply(&mut state);
734 assert_eq!(state, json!({"a": 1}));
735 }
736
737 #[test]
738 fn zip_combines_arrays() {
739 let mut state = json!({"names": ["a", "b", "c"], "scores": [10, 20, 30]});
740 S::zip(&["names", "scores"], "zipped").apply(&mut state);
741 assert_eq!(state["zipped"], json!([["a", 10], ["b", 20], ["c", 30]]));
742 }
743
744 #[test]
745 fn zip_truncates_to_shortest() {
746 let mut state = json!({"a": [1, 2, 3], "b": [10, 20]});
747 S::zip(&["a", "b"], "z").apply(&mut state);
748 assert_eq!(state["z"], json!([[1, 10], [2, 20]]));
749 }
750
751 #[test]
752 fn group_by_groups_elements() {
753 let mut state = json!({
754 "items": [
755 {"type": "fruit", "name": "apple"},
756 {"type": "veg", "name": "carrot"},
757 {"type": "fruit", "name": "banana"}
758 ]
759 });
760 S::group_by("items", "type", "grouped").apply(&mut state);
761 let grouped = &state["grouped"];
762 assert_eq!(grouped["fruit"].as_array().unwrap().len(), 2);
763 assert_eq!(grouped["veg"].as_array().unwrap().len(), 1);
764 }
765
766 #[test]
767 fn history_tracks_values() {
768 let mut state = json!({"score": 10});
769 let t = S::history("score", 3);
770 t.apply(&mut state);
771 state["score"] = json!(20);
772 t.apply(&mut state);
773 state["score"] = json!(30);
774 t.apply(&mut state);
775 state["score"] = json!(40);
776 t.apply(&mut state);
777 assert_eq!(state["score_history"], json!([20, 30, 40]));
779 }
780
781 #[test]
782 fn validate_passes_valid_state() {
783 let mut state = json!({"name": "Alice", "age": 30});
784 S::validate(json!({
785 "required": ["name", "age"],
786 "properties": {
787 "name": {"type": "string"},
788 "age": {"type": "number"}
789 }
790 }))
791 .apply(&mut state);
792 }
794
795 #[test]
796 #[should_panic(expected = "required key 'missing' missing from state")]
797 fn validate_fails_missing_required() {
798 let mut state = json!({"name": "Alice"});
799 S::validate(json!({"required": ["name", "missing"]})).apply(&mut state);
800 }
801
802 #[test]
803 #[should_panic(expected = "expected type 'string'")]
804 fn validate_fails_wrong_type() {
805 let mut state = json!({"name": 42});
806 S::validate(json!({
807 "properties": {"name": {"type": "string"}}
808 }))
809 .apply(&mut state);
810 }
811
812 #[test]
813 fn branch_takes_true_path() {
814 let mut state = json!({"premium": true});
815 S::branch(
816 |s| {
817 s.get("premium")
818 .and_then(serde_json::Value::as_bool)
819 .unwrap_or(false)
820 },
821 S::set("tier", json!("gold")),
822 S::set("tier", json!("basic")),
823 )
824 .apply(&mut state);
825 assert_eq!(state["tier"], "gold");
826 }
827
828 #[test]
829 fn branch_takes_false_path() {
830 let mut state = json!({"premium": false});
831 S::branch(
832 |s| {
833 s.get("premium")
834 .and_then(serde_json::Value::as_bool)
835 .unwrap_or(false)
836 },
837 S::set("tier", json!("gold")),
838 S::set("tier", json!("basic")),
839 )
840 .apply(&mut state);
841 assert_eq!(state["tier"], "basic");
842 }
843}