ixa/people/
event.rs

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