1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Serialize, Deserialize)]
5pub struct Module {
6 pub name: String,
8
9 pub actions: Vec<String>,
11
12 pub state_keys: Vec<String>,
14
15 pub persist: bool,
17
18 pub version: Option<u32>,
20}
21
22impl Module {
23 pub fn new(name: impl Into<String>) -> Self {
24 Self {
25 name: name.into(),
26 actions: Vec::new(),
27 state_keys: Vec::new(),
28 persist: false,
29 version: None,
30 }
31 }
32
33 pub fn with_actions(mut self, actions: Vec<String>) -> Self {
34 self.actions = actions;
35 self
36 }
37
38 pub fn with_state_keys(mut self, state_keys: Vec<String>) -> Self {
39 self.state_keys = state_keys;
40 self
41 }
42
43 pub fn with_persist(mut self, persist: bool) -> Self {
44 self.persist = persist;
45 self
46 }
47
48 pub fn with_version(mut self, version: u32) -> Self {
49 self.version = Some(version);
50 self
51 }
52}
53
54pub trait ModuleLifecycle {
56 fn on_created(&mut self);
58
59 fn on_destroyed(&mut self);
61
62 fn on_state_changed(&mut self, state: serde_json::Value);
64}
65
66pub type LifecycleCallback = Box<dyn Fn() + Send + Sync>;
68
69pub struct ModuleInstance {
71 pub module: Module,
73
74 pub state: serde_json::Value,
76
77 pub mounted: bool,
79
80 on_created: Option<LifecycleCallback>,
82
83 on_destroyed: Option<LifecycleCallback>,
85}
86
87impl ModuleInstance {
88 pub fn new(module: Module, initial_state: serde_json::Value) -> Self {
89 Self {
90 module,
91 state: initial_state,
92 mounted: false,
93 on_created: None,
94 on_destroyed: None,
95 }
96 }
97
98 pub fn set_on_created<F>(&mut self, callback: F)
100 where
101 F: Fn() + Send + Sync + 'static,
102 {
103 self.on_created = Some(Box::new(callback));
104 }
105
106 pub fn set_on_destroyed<F>(&mut self, callback: F)
108 where
109 F: Fn() + Send + Sync + 'static,
110 {
111 self.on_destroyed = Some(Box::new(callback));
112 }
113
114 pub fn mount(&mut self) {
116 if !self.mounted {
117 self.mounted = true;
118 if let Some(ref callback) = self.on_created {
120 callback();
121 }
122 }
123 }
124
125 pub fn unmount(&mut self) {
127 if self.mounted {
128 if let Some(ref callback) = self.on_destroyed {
130 callback();
131 }
132 self.mounted = false;
133 }
134 }
135
136 pub fn update_state(&mut self, patch: serde_json::Value) {
138 merge_json(&mut self.state, patch);
140 }
141
142 pub fn update_state_sparse(&mut self, paths: &[String], values: &serde_json::Value) {
145 if let serde_json::Value::Object(map) = values {
147 for path in paths {
148 if let Some(new_value) = map.get(path) {
149 set_value_at_path(&mut self.state, path, new_value.clone());
150 }
151 }
152 }
153 }
154
155 pub fn get_state(&self) -> &serde_json::Value {
157 &self.state
158 }
159}
160
161fn merge_json(target: &mut serde_json::Value, source: serde_json::Value) {
163 use serde_json::Value;
164
165 match (target, source) {
166 (Value::Object(target_map), Value::Object(source_map)) => {
167 for (key, value) in source_map {
168 if let Some(target_value) = target_map.get_mut(&key) {
169 merge_json(target_value, value);
170 } else {
171 target_map.insert(key, value);
172 }
173 }
174 }
175 (target, source) => {
176 *target = source;
177 }
178 }
179}
180
181fn set_value_at_path(target: &mut serde_json::Value, path: &str, value: serde_json::Value) {
184 use serde_json::Value;
185
186 let parts: Vec<&str> = path.split('.').collect();
187 if parts.is_empty() {
188 return;
189 }
190
191 let mut current = target;
192
193 for part in &parts[..parts.len() - 1] {
195 if let Ok(index) = part.parse::<usize>() {
197 if let Value::Array(arr) = current {
198 while arr.len() <= index {
200 arr.push(Value::Null);
201 }
202 current = &mut arr[index];
203 continue;
204 }
205 }
206
207 if !current.is_object() {
209 *current = Value::Object(serde_json::Map::new());
210 }
211
212 if let Value::Object(map) = current {
213 if !map.contains_key(*part) {
214 map.insert(part.to_string(), Value::Object(serde_json::Map::new()));
215 }
216 current = map.get_mut(*part).unwrap();
217 }
218 }
219
220 let final_key = parts[parts.len() - 1];
222
223 if let Ok(index) = final_key.parse::<usize>() {
225 if let Value::Array(arr) = current {
226 while arr.len() <= index {
227 arr.push(Value::Null);
228 }
229 arr[index] = value;
230 return;
231 }
232 }
233
234 if !current.is_object() {
236 *current = Value::Object(serde_json::Map::new());
237 }
238
239 if let Value::Object(map) = current {
240 map.insert(final_key.to_string(), value);
241 }
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247 use serde_json::json;
248
249 #[test]
250 fn test_merge_json() {
251 let mut target = json!({
252 "user": {
253 "name": "Alice",
254 "age": 30
255 }
256 });
257
258 let patch = json!({
259 "user": {
260 "age": 31
261 }
262 });
263
264 merge_json(&mut target, patch);
265
266 assert_eq!(target["user"]["name"], "Alice");
267 assert_eq!(target["user"]["age"], 31);
268 }
269
270 #[test]
271 fn test_lifecycle_on_created_callback() {
272 use std::sync::{Arc, Mutex};
273
274 let module = Module::new("TestModule");
275 let initial_state = json!({});
276 let mut instance = ModuleInstance::new(module, initial_state);
277
278 let called = Arc::new(Mutex::new(false));
280 let called_clone = called.clone();
281
282 instance.set_on_created(move || {
283 *called_clone.lock().unwrap() = true;
284 });
285
286 assert!(!*called.lock().unwrap());
288 instance.mount();
289 assert!(*called.lock().unwrap());
290 assert!(instance.mounted);
291 }
292
293 #[test]
294 fn test_lifecycle_on_destroyed_callback() {
295 use std::sync::{Arc, Mutex};
296
297 let module = Module::new("TestModule");
298 let initial_state = json!({});
299 let mut instance = ModuleInstance::new(module, initial_state);
300
301 let called = Arc::new(Mutex::new(false));
302 let called_clone = called.clone();
303
304 instance.set_on_destroyed(move || {
305 *called_clone.lock().unwrap() = true;
306 });
307
308 instance.mount();
310 assert!(instance.mounted);
311
312 assert!(!*called.lock().unwrap());
314 instance.unmount();
315 assert!(*called.lock().unwrap());
316 assert!(!instance.mounted);
317 }
318
319 #[test]
320 fn test_lifecycle_callbacks_not_called_when_not_set() {
321 let module = Module::new("TestModule");
322 let initial_state = json!({});
323 let mut instance = ModuleInstance::new(module, initial_state);
324
325 instance.mount();
327 assert!(instance.mounted);
328
329 instance.unmount();
330 assert!(!instance.mounted);
331 }
332
333 #[test]
334 fn test_lifecycle_mount_idempotent() {
335 use std::sync::{Arc, Mutex};
336
337 let module = Module::new("TestModule");
338 let initial_state = json!({});
339 let mut instance = ModuleInstance::new(module, initial_state);
340
341 let call_count = Arc::new(Mutex::new(0));
342 let call_count_clone = call_count.clone();
343
344 instance.set_on_created(move || {
345 *call_count_clone.lock().unwrap() += 1;
346 });
347
348 instance.mount();
350 assert_eq!(*call_count.lock().unwrap(), 1);
351
352 instance.mount();
354 assert_eq!(*call_count.lock().unwrap(), 1);
355 }
356
357 #[test]
358 fn test_lifecycle_unmount_idempotent() {
359 use std::sync::{Arc, Mutex};
360
361 let module = Module::new("TestModule");
362 let initial_state = json!({});
363 let mut instance = ModuleInstance::new(module, initial_state);
364
365 let call_count = Arc::new(Mutex::new(0));
366 let call_count_clone = call_count.clone();
367
368 instance.set_on_destroyed(move || {
369 *call_count_clone.lock().unwrap() += 1;
370 });
371
372 instance.mount();
374
375 instance.unmount();
377 assert_eq!(*call_count.lock().unwrap(), 1);
378
379 instance.unmount();
381 assert_eq!(*call_count.lock().unwrap(), 1);
382 }
383
384 #[test]
385 fn test_lifecycle_full_cycle() {
386 use std::sync::{Arc, Mutex};
387
388 let module = Module::new("TestModule");
389 let initial_state = json!({});
390 let mut instance = ModuleInstance::new(module, initial_state);
391
392 let events = Arc::new(Mutex::new(Vec::new()));
393 let events_created = events.clone();
394 let events_destroyed = events.clone();
395
396 instance.set_on_created(move || {
397 events_created.lock().unwrap().push("created");
398 });
399
400 instance.set_on_destroyed(move || {
401 events_destroyed.lock().unwrap().push("destroyed");
402 });
403
404 instance.mount();
406 instance.unmount();
407 instance.mount();
408 instance.unmount();
409
410 let events = events.lock().unwrap();
411 assert_eq!(events.len(), 4);
412 assert_eq!(events[0], "created");
413 assert_eq!(events[1], "destroyed");
414 assert_eq!(events[2], "created");
415 assert_eq!(events[3], "destroyed");
416 }
417
418 #[test]
419 fn test_set_value_at_path_simple() {
420 let mut state = json!({
421 "count": 0
422 });
423
424 set_value_at_path(&mut state, "count", json!(42));
425 assert_eq!(state["count"], 42);
426 }
427
428 #[test]
429 fn test_set_value_at_path_nested() {
430 let mut state = json!({
431 "user": {
432 "name": "Alice",
433 "profile": {
434 "bio": "Developer"
435 }
436 }
437 });
438
439 set_value_at_path(&mut state, "user.profile.bio", json!("Engineer"));
440 assert_eq!(state["user"]["profile"]["bio"], "Engineer");
441 assert_eq!(state["user"]["name"], "Alice");
443 }
444
445 #[test]
446 fn test_set_value_at_path_creates_intermediate() {
447 let mut state = json!({});
448
449 set_value_at_path(&mut state, "user.profile.name", json!("Bob"));
450 assert_eq!(state["user"]["profile"]["name"], "Bob");
451 }
452
453 #[test]
454 fn test_set_value_at_path_array_index() {
455 let mut state = json!({
456 "items": ["a", "b", "c"]
457 });
458
459 set_value_at_path(&mut state, "items.1", json!("modified"));
460 assert_eq!(state["items"][1], "modified");
461 assert_eq!(state["items"][0], "a");
462 assert_eq!(state["items"][2], "c");
463 }
464
465 #[test]
466 fn test_update_state_sparse_single_path() {
467 let module = Module::new("TestModule");
468 let initial_state = json!({
469 "count": 0,
470 "name": "Alice"
471 });
472 let mut instance = ModuleInstance::new(module, initial_state);
473
474 let paths = vec!["count".to_string()];
475 let values = json!({
476 "count": 42
477 });
478
479 instance.update_state_sparse(&paths, &values);
480
481 assert_eq!(instance.get_state()["count"], 42);
482 assert_eq!(instance.get_state()["name"], "Alice"); }
484
485 #[test]
486 fn test_update_state_sparse_nested_path() {
487 let module = Module::new("TestModule");
488 let initial_state = json!({
489 "user": {
490 "name": "Alice",
491 "age": 30
492 },
493 "settings": {
494 "theme": "dark"
495 }
496 });
497 let mut instance = ModuleInstance::new(module, initial_state);
498
499 let paths = vec!["user.age".to_string()];
500 let values = json!({
501 "user.age": 31
502 });
503
504 instance.update_state_sparse(&paths, &values);
505
506 assert_eq!(instance.get_state()["user"]["age"], 31);
507 assert_eq!(instance.get_state()["user"]["name"], "Alice"); assert_eq!(instance.get_state()["settings"]["theme"], "dark"); }
510
511 #[test]
512 fn test_update_state_sparse_multiple_paths() {
513 let module = Module::new("TestModule");
514 let initial_state = json!({
515 "count": 0,
516 "user": {
517 "name": "Alice"
518 }
519 });
520 let mut instance = ModuleInstance::new(module, initial_state);
521
522 let paths = vec!["count".to_string(), "user.name".to_string()];
523 let values = json!({
524 "count": 100,
525 "user.name": "Bob"
526 });
527
528 instance.update_state_sparse(&paths, &values);
529
530 assert_eq!(instance.get_state()["count"], 100);
531 assert_eq!(instance.get_state()["user"]["name"], "Bob");
532 }
533
534 #[test]
537 fn test_set_value_at_path_empty_path() {
538 let mut state = json!({"count": 0});
539 set_value_at_path(&mut state, "", json!(42));
541 assert_eq!(state["count"], 0);
542 }
543
544 #[test]
545 fn test_set_value_at_path_null_value() {
546 let mut state = json!({
547 "user": {
548 "name": "Alice",
549 "email": "alice@example.com"
550 }
551 });
552
553 set_value_at_path(&mut state, "user.email", json!(null));
554 assert_eq!(state["user"]["email"], serde_json::Value::Null);
555 assert_eq!(state["user"]["name"], "Alice"); }
557
558 #[test]
559 fn test_set_value_at_path_type_change() {
560 let mut state = json!({
561 "data": "string value"
562 });
563
564 set_value_at_path(&mut state, "data", json!({"nested": true}));
566 assert_eq!(state["data"]["nested"], true);
567
568 set_value_at_path(&mut state, "data", json!([1, 2, 3]));
570 assert_eq!(state["data"][0], 1);
571
572 set_value_at_path(&mut state, "data", json!(42));
574 assert_eq!(state["data"], 42);
575 }
576
577 #[test]
578 fn test_set_value_at_path_deeply_nested() {
579 let mut state = json!({});
580
581 set_value_at_path(&mut state, "a.b.c.d.e.f", json!("deep value"));
583 assert_eq!(state["a"]["b"]["c"]["d"]["e"]["f"], "deep value");
584 }
585
586 #[test]
587 fn test_set_value_at_path_nested_array_object() {
588 let mut state = json!({
589 "users": [
590 {"name": "Alice", "tags": ["admin"]},
591 {"name": "Bob", "tags": ["user"]}
592 ]
593 });
594
595 set_value_at_path(&mut state, "users.1.name", json!("Robert"));
597 assert_eq!(state["users"][1]["name"], "Robert");
598 assert_eq!(state["users"][0]["name"], "Alice"); set_value_at_path(&mut state, "users.0.tags.0", json!("superadmin"));
602 assert_eq!(state["users"][0]["tags"][0], "superadmin");
603 }
604
605 #[test]
606 fn test_set_value_at_path_extend_array() {
607 let mut state = json!({
608 "items": ["a", "b"]
609 });
610
611 set_value_at_path(&mut state, "items.5", json!("extended"));
613 assert_eq!(state["items"].as_array().unwrap().len(), 6);
614 assert_eq!(state["items"][5], "extended");
615 assert_eq!(state["items"][2], serde_json::Value::Null);
616 assert_eq!(state["items"][3], serde_json::Value::Null);
617 assert_eq!(state["items"][4], serde_json::Value::Null);
618 }
619
620 #[test]
621 fn test_set_value_at_path_overwrite_primitive_with_nested() {
622 let mut state = json!({
623 "config": 42
624 });
625
626 set_value_at_path(&mut state, "config.nested.value", json!("test"));
629 assert_eq!(state["config"]["nested"]["value"], "test");
630 }
631
632 #[test]
633 fn test_set_value_at_path_boolean_values() {
634 let mut state = json!({
635 "flags": {
636 "enabled": true,
637 "visible": false
638 }
639 });
640
641 set_value_at_path(&mut state, "flags.enabled", json!(false));
642 set_value_at_path(&mut state, "flags.visible", json!(true));
643 assert_eq!(state["flags"]["enabled"], false);
644 assert_eq!(state["flags"]["visible"], true);
645 }
646
647 #[test]
648 fn test_set_value_at_path_float_values() {
649 let mut state = json!({
650 "coordinates": {
651 "lat": 0.0,
652 "lng": 0.0
653 }
654 });
655
656 set_value_at_path(&mut state, "coordinates.lat", json!(37.7749));
657 set_value_at_path(&mut state, "coordinates.lng", json!(-122.4194));
658
659 let lat = state["coordinates"]["lat"].as_f64().unwrap();
661 let lng = state["coordinates"]["lng"].as_f64().unwrap();
662 assert!((lat - 37.7749).abs() < 0.0001);
663 assert!((lng - (-122.4194)).abs() < 0.0001);
664 }
665
666 #[test]
667 fn test_update_state_sparse_empty_paths() {
668 let module = Module::new("TestModule");
669 let initial_state = json!({
670 "count": 0
671 });
672 let mut instance = ModuleInstance::new(module, initial_state);
673
674 let paths: Vec<String> = vec![];
675 let values = json!({});
676
677 instance.update_state_sparse(&paths, &values);
679 assert_eq!(instance.get_state()["count"], 0);
680 }
681
682 #[test]
683 fn test_update_state_sparse_path_not_in_values() {
684 let module = Module::new("TestModule");
685 let initial_state = json!({
686 "count": 0,
687 "name": "Alice"
688 });
689 let mut instance = ModuleInstance::new(module, initial_state);
690
691 let paths = vec!["count".to_string(), "missing".to_string()];
693 let values = json!({
694 "count": 42
695 });
697
698 instance.update_state_sparse(&paths, &values);
699 assert_eq!(instance.get_state()["count"], 42);
700 assert_eq!(instance.get_state()["name"], "Alice");
701 }
702
703 #[test]
704 fn test_update_state_sparse_invalid_values_type() {
705 let module = Module::new("TestModule");
706 let initial_state = json!({
707 "count": 0
708 });
709 let mut instance = ModuleInstance::new(module, initial_state);
710
711 let paths = vec!["count".to_string()];
713 let values = json!("not an object");
714
715 instance.update_state_sparse(&paths, &values);
716 assert_eq!(instance.get_state()["count"], 0);
718 }
719
720 #[test]
721 fn test_update_state_sparse_array_values() {
722 let module = Module::new("TestModule");
723 let initial_state = json!({
724 "items": []
725 });
726 let mut instance = ModuleInstance::new(module, initial_state);
727
728 let paths = vec!["items".to_string()];
729 let values = json!({
730 "items": ["a", "b", "c"]
731 });
732
733 instance.update_state_sparse(&paths, &values);
734 assert_eq!(instance.get_state()["items"], json!(["a", "b", "c"]));
735 }
736
737 #[test]
738 fn test_update_state_sparse_complex_nested_update() {
739 let module = Module::new("TestModule");
740 let initial_state = json!({
741 "app": {
742 "ui": {
743 "theme": "light",
744 "sidebar": {
745 "collapsed": false,
746 "width": 250
747 }
748 },
749 "data": {
750 "users": [],
751 "cache": {}
752 }
753 }
754 });
755 let mut instance = ModuleInstance::new(module, initial_state);
756
757 let paths = vec![
758 "app.ui.theme".to_string(),
759 "app.ui.sidebar.collapsed".to_string(),
760 "app.data.users".to_string(),
761 ];
762 let values = json!({
763 "app.ui.theme": "dark",
764 "app.ui.sidebar.collapsed": true,
765 "app.data.users": [{"id": 1, "name": "Alice"}]
766 });
767
768 instance.update_state_sparse(&paths, &values);
769
770 assert_eq!(instance.get_state()["app"]["ui"]["theme"], "dark");
771 assert_eq!(instance.get_state()["app"]["ui"]["sidebar"]["collapsed"], true);
772 assert_eq!(instance.get_state()["app"]["ui"]["sidebar"]["width"], 250); assert_eq!(instance.get_state()["app"]["data"]["users"][0]["name"], "Alice");
774 assert!(instance.get_state()["app"]["data"]["cache"].is_object()); }
776
777 #[test]
778 fn test_update_state_sparse_preserves_sibling_keys() {
779 let module = Module::new("TestModule");
780 let initial_state = json!({
781 "user": {
782 "name": "Alice",
783 "email": "alice@example.com",
784 "profile": {
785 "bio": "Developer",
786 "avatar": "alice.png",
787 "social": {
788 "twitter": "@alice",
789 "github": "alice"
790 }
791 }
792 }
793 });
794 let mut instance = ModuleInstance::new(module, initial_state);
795
796 let paths = vec!["user.profile.social.twitter".to_string()];
798 let values = json!({
799 "user.profile.social.twitter": "@alice_new"
800 });
801
802 instance.update_state_sparse(&paths, &values);
803
804 assert_eq!(instance.get_state()["user"]["profile"]["social"]["twitter"], "@alice_new");
806
807 assert_eq!(instance.get_state()["user"]["name"], "Alice");
809 assert_eq!(instance.get_state()["user"]["email"], "alice@example.com");
810 assert_eq!(instance.get_state()["user"]["profile"]["bio"], "Developer");
811 assert_eq!(instance.get_state()["user"]["profile"]["avatar"], "alice.png");
812 assert_eq!(instance.get_state()["user"]["profile"]["social"]["github"], "alice");
813 }
814}