ixa/people/
event.rs

1use crate::{Context, ContextPeopleExt, IxaEvent, PersonId, PersonProperty};
2use ixa_derive::IxaEvent;
3
4/// Emitted when a new person is created
5/// These should not be emitted outside this module
6#[derive(Clone, Copy, IxaEvent)]
7#[allow(clippy::manual_non_exhaustive)]
8pub struct PersonCreatedEvent {
9    /// The [`PersonId`] of the new person.
10    pub person_id: PersonId,
11}
12
13/// Emitted when a person property is updated
14/// These should not be emitted outside this module
15#[derive(Copy, Clone)]
16#[allow(clippy::manual_non_exhaustive)]
17pub struct PersonPropertyChangeEvent<T: PersonProperty> {
18    /// The [`PersonId`] that changed
19    pub person_id: PersonId,
20    /// The new value
21    pub current: T::Value,
22    /// The old value
23    pub previous: T::Value,
24}
25
26impl<T: PersonProperty> IxaEvent for PersonPropertyChangeEvent<T> {
27    fn on_subscribe(context: &mut Context) {
28        if T::is_derived() {
29            context.register_property::<T>();
30        }
31    }
32}
33
34#[cfg(test)]
35mod tests {
36    use crate::{
37        define_derived_property, define_global_property, define_person_property,
38        define_person_property_with_default, Context, ContextPeopleExt, PersonCreatedEvent,
39        PersonId, PersonPropertyChangeEvent,
40    };
41    use serde_derive::Serialize;
42    use std::cell::RefCell;
43    use std::rc::Rc;
44
45    define_person_property!(Age, u8);
46    #[derive(Serialize, Copy, Clone, Debug, PartialEq, Eq, Hash)]
47    pub enum AgeGroupValue {
48        Child,
49        Adult,
50    }
51    define_global_property!(Threshold, u8);
52
53    define_derived_property!(AgeGroup, AgeGroupValue, [Age], |age| {
54        if age < 18 {
55            AgeGroupValue::Child
56        } else {
57            AgeGroupValue::Adult
58        }
59    });
60
61    #[derive(Serialize, Copy, Clone, PartialEq, Eq, Debug)]
62    pub enum RiskCategoryValue {
63        High,
64        Low,
65    }
66    define_person_property!(RiskCategory, RiskCategoryValue);
67    define_person_property_with_default!(IsRunner, bool, false);
68    define_person_property!(RunningShoes, u8, |context: &Context, person: PersonId| {
69        let is_runner = context.get_person_property(person, IsRunner);
70        if is_runner {
71            4
72        } else {
73            0
74        }
75    });
76
77    #[test]
78    fn observe_person_addition() {
79        let mut context = Context::new();
80
81        let flag = Rc::new(RefCell::new(false));
82        let flag_clone = flag.clone();
83        context.subscribe_to_event(move |_context, event: PersonCreatedEvent| {
84            *flag_clone.borrow_mut() = true;
85            assert_eq!(event.person_id.0, 0);
86        });
87
88        let _ = context.add_person(()).unwrap();
89        context.execute();
90        assert!(*flag.borrow());
91    }
92
93    #[test]
94    fn observe_person_property_change() {
95        let mut context = Context::new();
96
97        let flag = Rc::new(RefCell::new(false));
98        let flag_clone = flag.clone();
99        context.subscribe_to_event(
100            move |_context, event: PersonPropertyChangeEvent<RiskCategory>| {
101                *flag_clone.borrow_mut() = true;
102                assert_eq!(event.person_id.0, 0, "Person id is correct");
103                assert_eq!(
104                    event.previous,
105                    RiskCategoryValue::Low,
106                    "Previous value is correct"
107                );
108                assert_eq!(
109                    event.current,
110                    RiskCategoryValue::High,
111                    "Current value is correct"
112                );
113            },
114        );
115        let person_id = context
116            .add_person((RiskCategory, RiskCategoryValue::Low))
117            .unwrap();
118        context.set_person_property(person_id, RiskCategory, RiskCategoryValue::High);
119        context.execute();
120        assert!(*flag.borrow());
121    }
122
123    #[test]
124    fn observe_person_property_change_with_set() {
125        let mut context = Context::new();
126
127        let flag = Rc::new(RefCell::new(false));
128        let flag_clone = flag.clone();
129        context.subscribe_to_event(
130            move |_context, _event: PersonPropertyChangeEvent<RunningShoes>| {
131                *flag_clone.borrow_mut() = true;
132            },
133        );
134        let person_id = context.add_person(()).unwrap();
135        // Initializer called as a side effect of set, so event fires.
136        context.set_person_property(person_id, RunningShoes, 42);
137        context.execute();
138        assert!(*flag.borrow());
139    }
140
141    #[test]
142    fn get_person_property_change_event() {
143        let mut context = Context::new();
144        let person = context.add_person((Age, 17)).unwrap();
145
146        let flag = Rc::new(RefCell::new(false));
147
148        let flag_clone = flag.clone();
149        context.subscribe_to_event(
150            move |_context, event: PersonPropertyChangeEvent<AgeGroup>| {
151                assert_eq!(event.person_id.0, 0);
152                assert_eq!(event.previous, AgeGroupValue::Child);
153                assert_eq!(event.current, AgeGroupValue::Adult);
154                *flag_clone.borrow_mut() = true;
155            },
156        );
157        context.set_person_property(person, Age, 18);
158        context.execute();
159        assert!(*flag.borrow());
160    }
161
162    #[test]
163    fn test_person_property_change_event_no_people() {
164        let mut context = Context::new();
165        // Non derived person property -- no problems
166        context.subscribe_to_event(|_context, _event: PersonPropertyChangeEvent<IsRunner>| {
167            unreachable!();
168        });
169
170        // Derived person property -- can't add an event without people being present
171        context.subscribe_to_event(|_context, _event: PersonPropertyChangeEvent<AgeGroup>| {
172            unreachable!();
173        });
174    }
175}