1use canwu_core::{BoundaryId, CommandId, EntityRef, EventId, KnowledgeHolderRef, PersonId};
4use canwu_time::SimTime;
5use serde::{
6 Deserialize, Deserializer, Serialize, Serializer,
7 de::{self, DeserializeOwned, MapAccess, SeqAccess, Visitor},
8 ser::SerializeMap,
9};
10use serde_json::{Number, Value};
11use std::collections::BTreeMap;
12use std::error::Error;
13use std::fmt::{self, Display, Formatter};
14
15#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
16#[serde(tag = "type", content = "id", rename_all = "snake_case")]
17pub enum CauseRef {
18 Boundary(BoundaryId),
19 Command(CommandId),
20 Event(EventId),
21 System(String),
22}
23
24#[derive(Clone, Debug)]
31pub struct EventKind {
32 event_type: String,
33 fields: Vec<(String, EventValue)>,
34}
35
36#[derive(Clone, Debug)]
37enum EventValue {
38 Null,
39 Bool(bool),
40 Number(Number),
41 String(String),
42 Array(Vec<Self>),
43 Object(Vec<(String, Self)>),
44}
45
46impl PartialEq for EventValue {
47 fn eq(&self, other: &Self) -> bool {
48 match (self, other) {
49 (Self::Null, Self::Null) => true,
50 (Self::Bool(left), Self::Bool(right)) => left == right,
51 (Self::Number(left), Self::Number(right)) => left == right,
52 (Self::String(left), Self::String(right)) => left == right,
53 (Self::Array(left), Self::Array(right)) => left == right,
54 (Self::Object(left), Self::Object(right)) => {
55 left.len() == right.len()
56 && left.iter().all(|(key, value)| {
57 right
58 .iter()
59 .find_map(|(candidate, value)| (candidate == key).then_some(value))
60 == Some(value)
61 })
62 }
63 _ => false,
64 }
65 }
66}
67
68impl Eq for EventValue {}
69
70impl PartialEq for EventKind {
71 fn eq(&self, other: &Self) -> bool {
72 self.event_type == other.event_type
73 && self.fields.len() == other.fields.len()
74 && self.fields.iter().all(|(key, value)| {
75 other
76 .field_value(key)
77 .is_some_and(|candidate| candidate == value)
78 })
79 }
80}
81
82impl Eq for EventKind {}
83
84impl EventValue {
85 fn from_json(value: Value) -> Self {
86 match value {
87 Value::Null => Self::Null,
88 Value::Bool(value) => Self::Bool(value),
89 Value::Number(value) => Self::Number(value),
90 Value::String(value) => Self::String(value),
91 Value::Array(values) => Self::Array(values.into_iter().map(Self::from_json).collect()),
92 Value::Object(fields) => Self::Object(
93 fields
94 .into_iter()
95 .map(|(key, value)| (key, Self::from_json(value)))
96 .collect(),
97 ),
98 }
99 }
100
101 fn into_json(self) -> Value {
102 match self {
103 Self::Null => Value::Null,
104 Self::Bool(value) => Value::Bool(value),
105 Self::Number(value) => Value::Number(value),
106 Self::String(value) => Value::String(value),
107 Self::Array(values) => Value::Array(values.into_iter().map(Self::into_json).collect()),
108 Self::Object(fields) => Value::Object(
109 fields
110 .into_iter()
111 .map(|(key, value)| (key, value.into_json()))
112 .collect(),
113 ),
114 }
115 }
116
117 fn as_str(&self) -> Option<&str> {
118 match self {
119 Self::String(value) => Some(value),
120 _ => None,
121 }
122 }
123}
124
125impl Serialize for EventValue {
126 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
127 where
128 S: Serializer,
129 {
130 match self {
131 Self::Null => serializer.serialize_none(),
132 Self::Bool(value) => serializer.serialize_bool(*value),
133 Self::Number(value) => value.serialize(serializer),
134 Self::String(value) => serializer.serialize_str(value),
135 Self::Array(values) => values.serialize(serializer),
136 Self::Object(fields) => {
137 let mut map = serializer.serialize_map(Some(fields.len()))?;
138 let mut ordered = fields.iter().collect::<Vec<_>>();
139 ordered.sort_by(|left, right| left.0.cmp(&right.0));
140 for (key, value) in ordered {
141 map.serialize_entry(key, value)?;
142 }
143 map.end()
144 }
145 }
146 }
147}
148
149impl<'de> Deserialize<'de> for EventValue {
150 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
151 where
152 D: Deserializer<'de>,
153 {
154 struct EventValueVisitor;
155
156 impl<'de> Visitor<'de> for EventValueVisitor {
157 type Value = EventValue;
158
159 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
160 formatter.write_str("a JSON-compatible event field value")
161 }
162
163 fn visit_unit<E>(self) -> Result<Self::Value, E> {
164 Ok(EventValue::Null)
165 }
166
167 fn visit_none<E>(self) -> Result<Self::Value, E> {
168 Ok(EventValue::Null)
169 }
170
171 fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
172 where
173 D: Deserializer<'de>,
174 {
175 EventValue::deserialize(deserializer)
176 }
177
178 fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E> {
179 Ok(EventValue::Bool(value))
180 }
181
182 fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E> {
183 Ok(EventValue::Number(value.into()))
184 }
185
186 fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E> {
187 Ok(EventValue::Number(value.into()))
188 }
189
190 fn visit_f64<E>(self, value: f64) -> Result<Self::Value, E>
191 where
192 E: de::Error,
193 {
194 Number::from_f64(value)
195 .map(EventValue::Number)
196 .ok_or_else(|| E::custom("event field contains a non-finite number"))
197 }
198
199 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> {
200 Ok(EventValue::String(value.to_owned()))
201 }
202
203 fn visit_string<E>(self, value: String) -> Result<Self::Value, E> {
204 Ok(EventValue::String(value))
205 }
206
207 fn visit_seq<A>(self, mut sequence: A) -> Result<Self::Value, A::Error>
208 where
209 A: SeqAccess<'de>,
210 {
211 let mut values = Vec::with_capacity(sequence.size_hint().unwrap_or(0));
212 while let Some(value) = sequence.next_element()? {
213 values.push(value);
214 }
215 Ok(EventValue::Array(values))
216 }
217
218 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
219 where
220 A: MapAccess<'de>,
221 {
222 let mut fields = Vec::with_capacity(map.size_hint().unwrap_or(0));
223 while let Some(key) = map.next_key::<String>()? {
224 if fields.iter().any(|(existing, _)| existing == &key) {
225 return Err(de::Error::custom(format!(
226 "event field object contains duplicate key {key}"
227 )));
228 }
229 fields.push((key, map.next_value()?));
230 }
231 Ok(EventValue::Object(fields))
232 }
233 }
234
235 deserializer.deserialize_any(EventValueVisitor)
236 }
237}
238
239impl Serialize for EventKind {
240 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
241 where
242 S: Serializer,
243 {
244 let mut map = serializer.serialize_map(Some(self.fields.len() + 1))?;
245 map.serialize_entry("type", &self.event_type)?;
246 let mut ordered = self.fields.iter().collect::<Vec<_>>();
247 ordered.sort_by(|left, right| left.0.cmp(&right.0));
248 for (key, value) in ordered {
249 map.serialize_entry(key, value)?;
250 }
251 map.end()
252 }
253}
254
255impl<'de> Deserialize<'de> for EventKind {
256 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
257 where
258 D: Deserializer<'de>,
259 {
260 struct EventKindVisitor;
261
262 impl<'de> Visitor<'de> for EventKindVisitor {
263 type Value = EventKind;
264
265 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
266 formatter.write_str("an event object with a type field")
267 }
268
269 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
270 where
271 A: MapAccess<'de>,
272 {
273 let mut event_type = None;
274 let mut fields = Vec::with_capacity(map.size_hint().unwrap_or(1).saturating_sub(1));
275 while let Some(key) = map.next_key::<String>()? {
276 if key == "type" {
277 if event_type.is_some() {
278 return Err(de::Error::duplicate_field("type"));
279 }
280 event_type = Some(map.next_value::<String>()?);
281 } else {
282 if fields.iter().any(|(existing, _)| existing == &key) {
283 return Err(de::Error::custom(format!(
284 "event payload contains duplicate field {key}"
285 )));
286 }
287 fields.push((key, map.next_value()?));
288 }
289 }
290 let event_type = event_type.ok_or_else(|| de::Error::missing_field("type"))?;
291 if event_type.is_empty() {
292 return Err(de::Error::custom("event type cannot be empty"));
293 }
294 Ok(EventKind { event_type, fields })
295 }
296 }
297
298 deserializer.deserialize_map(EventKindVisitor)
299 }
300}
301
302#[derive(Deserialize)]
303struct OrderedFields(
304 #[serde(deserialize_with = "deserialize_ordered_fields")] Vec<(String, EventValue)>,
305);
306
307fn deserialize_ordered_fields<'de, D>(
308 deserializer: D,
309) -> Result<Vec<(String, EventValue)>, D::Error>
310where
311 D: Deserializer<'de>,
312{
313 struct OrderedFieldsVisitor;
314
315 impl<'de> Visitor<'de> for OrderedFieldsVisitor {
316 type Value = Vec<(String, EventValue)>;
317
318 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
319 formatter.write_str("an event payload object")
320 }
321
322 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
323 where
324 A: MapAccess<'de>,
325 {
326 let mut fields = Vec::with_capacity(map.size_hint().unwrap_or(0));
327 while let Some(key) = map.next_key::<String>()? {
328 if fields.iter().any(|(existing, _)| existing == &key) {
329 return Err(de::Error::custom(format!(
330 "event payload contains duplicate field {key}"
331 )));
332 }
333 fields.push((key, map.next_value()?));
334 }
335 Ok(fields)
336 }
337 }
338
339 deserializer.deserialize_map(OrderedFieldsVisitor)
340}
341
342#[derive(Debug)]
343pub enum EventKindError {
344 EmptyEventType,
345 ReservedTypeField,
346 PayloadMustBeObject,
347 MissingField(String),
348 UnexpectedEventType {
349 expected: &'static str,
350 actual: String,
351 },
352 InvalidPayload(serde_json::Error),
353}
354
355impl Display for EventKindError {
356 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
357 match self {
358 Self::EmptyEventType => formatter.write_str("event type cannot be empty"),
359 Self::ReservedTypeField => {
360 formatter.write_str("event payload cannot contain the reserved type field")
361 }
362 Self::PayloadMustBeObject => {
363 formatter.write_str("event payload must serialize as an object")
364 }
365 Self::MissingField(field) => {
366 write!(formatter, "event payload is missing field {field}")
367 }
368 Self::UnexpectedEventType { expected, actual } => {
369 write!(formatter, "expected event type {expected}, found {actual}")
370 }
371 Self::InvalidPayload(error) => Display::fmt(error, formatter),
372 }
373 }
374}
375
376impl Error for EventKindError {
377 fn source(&self) -> Option<&(dyn Error + 'static)> {
378 match self {
379 Self::InvalidPayload(error) => Some(error),
380 Self::EmptyEventType
381 | Self::ReservedTypeField
382 | Self::PayloadMustBeObject
383 | Self::MissingField(_)
384 | Self::UnexpectedEventType { .. } => None,
385 }
386 }
387}
388
389impl From<serde_json::Error> for EventKindError {
390 fn from(error: serde_json::Error) -> Self {
391 Self::InvalidPayload(error)
392 }
393}
394
395#[derive(Clone, Debug, Default, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
402#[serde(tag = "type", content = "value", rename_all = "snake_case")]
403pub enum EventAudience {
404 Public,
405 Actor(PersonId),
406 Actors(Vec<PersonId>),
407 KnowledgeHolder(KnowledgeHolderRef),
408 AffectedActors,
411 #[default]
412 Private,
413}
414
415impl EventKind {
416 pub fn from_fields(
423 event_type: impl Into<String>,
424 fields: BTreeMap<String, Value>,
425 ) -> Result<Self, EventKindError> {
426 let event_type = event_type.into();
427 if event_type.is_empty() {
428 return Err(EventKindError::EmptyEventType);
429 }
430 if fields.contains_key("type") {
431 return Err(EventKindError::ReservedTypeField);
432 }
433 Ok(Self {
434 event_type,
435 fields: fields
436 .into_iter()
437 .map(|(key, value)| (key, EventValue::from_json(value)))
438 .collect(),
439 })
440 }
441
442 pub fn from_payload<T: Serialize>(
450 event_type: impl Into<String>,
451 payload: &T,
452 ) -> Result<Self, EventKindError> {
453 let encoded = serde_json::to_vec(payload)?;
454 let OrderedFields(fields) =
455 serde_json::from_slice(&encoded).map_err(|_| EventKindError::PayloadMustBeObject)?;
456 let event_type = event_type.into();
457 if event_type.is_empty() {
458 return Err(EventKindError::EmptyEventType);
459 }
460 if fields.iter().any(|(key, _)| key == "type") {
461 return Err(EventKindError::ReservedTypeField);
462 }
463 Ok(Self { event_type, fields })
464 }
465
466 #[must_use]
468 pub fn plugin(plugin: impl Into<String>, event_type: impl Into<String>) -> Self {
469 Self {
470 event_type: "plugin".to_owned(),
471 fields: vec![
472 ("plugin".to_owned(), EventValue::String(plugin.into())),
473 (
474 "event_type".to_owned(),
475 EventValue::String(event_type.into()),
476 ),
477 ],
478 }
479 }
480
481 #[must_use]
482 pub fn event_type(&self) -> &str {
483 &self.event_type
484 }
485
486 #[must_use]
487 pub fn is_type(&self, event_type: &str) -> bool {
488 self.event_type == event_type
489 }
490
491 #[must_use]
492 pub fn fields(&self) -> BTreeMap<String, Value> {
493 self.fields
494 .iter()
495 .cloned()
496 .map(|(key, value)| (key, value.into_json()))
497 .collect()
498 }
499
500 #[must_use]
501 pub fn field(&self, name: &str) -> Option<Value> {
502 self.field_value(name).cloned().map(EventValue::into_json)
503 }
504
505 pub fn set_field<T: Serialize>(&mut self, name: &str, value: &T) -> Result<(), EventKindError> {
512 let encoded = serde_json::to_vec(value)?;
513 let replacement = serde_json::from_slice::<EventValue>(&encoded)?;
514 let field = self
515 .fields
516 .iter_mut()
517 .find(|(key, _)| key == name)
518 .ok_or_else(|| EventKindError::MissingField(name.to_owned()))?;
519 field.1 = replacement;
520 Ok(())
521 }
522
523 pub fn decode_payload<T: DeserializeOwned>(&self) -> Result<T, EventKindError> {
529 struct Payload<'a>(&'a [(String, EventValue)]);
530
531 impl Serialize for Payload<'_> {
532 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
533 where
534 S: Serializer,
535 {
536 let mut map = serializer.serialize_map(Some(self.0.len()))?;
537 for (key, value) in self.0 {
538 map.serialize_entry(key, value)?;
539 }
540 map.end()
541 }
542 }
543
544 let encoded = serde_json::to_vec(&Payload(&self.fields))?;
545 Ok(serde_json::from_slice(&encoded)?)
546 }
547
548 pub fn decode_field<T: DeserializeOwned>(&self, name: &str) -> Result<T, EventKindError> {
555 let value = self
556 .field_value(name)
557 .ok_or_else(|| EventKindError::MissingField(name.to_owned()))?;
558 let encoded = serde_json::to_vec(value)?;
559 Ok(serde_json::from_slice(&encoded)?)
560 }
561
562 #[must_use]
563 pub fn plugin_identity(&self) -> Option<(&str, &str)> {
564 if self.event_type != "plugin" || self.fields.len() != 2 {
565 return None;
566 }
567 Some((
568 self.field_value("plugin")?.as_str()?,
569 self.field_value("event_type")?.as_str()?,
570 ))
571 }
572
573 #[must_use]
581 pub fn qualified_event_type(&self) -> String {
582 self.plugin_identity().map_or_else(
583 || self.event_type.clone(),
584 |(plugin, event_type)| format!("{plugin}.{event_type}"),
585 )
586 }
587
588 fn field_value(&self, name: &str) -> Option<&EventValue> {
589 self.fields
590 .iter()
591 .find_map(|(key, value)| (key == name).then_some(value))
592 }
593}
594
595#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
596pub struct SimEvent {
597 pub id: EventId,
598 pub timestamp: SimTime,
599 pub kind: EventKind,
600 pub affected_entities: Vec<EntityRef>,
601 pub summary: String,
602 pub cause: Option<CauseRef>,
603 pub correlation_id: u64,
604}
605
606#[cfg(test)]
607mod tests {
608 use super::EventKind;
609 use canwu_core::{ArmyId, TerritoryId};
610 use serde::{Deserialize, Serialize};
611 use serde_json::json;
612
613 #[derive(Debug, Deserialize, Eq, PartialEq, Serialize)]
614 struct ArmyArrived {
615 army: ArmyId,
616 territory: TerritoryId,
617 }
618
619 #[test]
620 fn qualified_event_type_preserves_legacy_labels_and_disambiguates_plugins() {
621 let payload = ArmyArrived {
622 army: ArmyId::new(1),
623 territory: TerritoryId::new(2),
624 };
625 let built_in = EventKind::from_payload("army_arrived", &payload).unwrap();
626 assert_eq!(built_in.event_type(), "army_arrived");
627 assert_eq!(built_in.qualified_event_type(), "army_arrived");
628 assert_eq!(built_in.decode_payload::<ArmyArrived>().unwrap(), payload);
629 assert_eq!(
630 serde_json::to_value(&built_in).unwrap(),
631 json!({"type": "army_arrived", "army": 1, "territory": 2})
632 );
633
634 let supply = EventKind::plugin("example-supply", "grain_allocated");
635 let demand = EventKind::plugin("example-demand", "grain_allocated");
636
637 assert_eq!(supply.event_type(), "plugin");
638 assert_eq!(
639 supply.qualified_event_type(),
640 "example-supply.grain_allocated"
641 );
642 assert_eq!(
643 demand.qualified_event_type(),
644 "example-demand.grain_allocated"
645 );
646 assert_ne!(supply.qualified_event_type(), demand.qualified_event_type());
647 assert_eq!(
648 serde_json::to_value(&supply).unwrap(),
649 json!({
650 "type": "plugin",
651 "plugin": "example-supply",
652 "event_type": "grain_allocated"
653 })
654 );
655 assert_eq!(
656 serde_json::to_string(&supply).unwrap(),
657 r#"{"type":"plugin","event_type":"grain_allocated","plugin":"example-supply"}"#
658 );
659 }
660
661 #[test]
662 fn event_wire_shapes_round_trip_in_canonical_field_order() {
663 let fixtures = [
664 r#"{"type":"move_ordered","army":1,"arrival_at":60,"from":2,"to":3}"#,
665 r#"{"type":"army_arrived","army":1,"territory":3}"#,
666 r#"{"type":"person_move_ordered","arrival_at":60,"from":2,"person":4,"to":3}"#,
667 r#"{"type":"person_arrived","person":4,"territory":3}"#,
668 r#"{"type":"letter_delivered","carrier":4,"letter":5,"recipient":6,"territory":3}"#,
669 r#"{"type":"report_dispatched","army":1,"arrives_at":90,"recipient":4}"#,
670 r#"{"type":"knowledge_updated","army":1,"known_location":3,"recipient":4}"#,
671 r#"{"type":"knowledge_published","holder":{"id":4,"type":"person"},"record_count":2}"#,
672 r#"{"type":"debug_field_changed","entity":{"id":1,"type":"army"},"field":"morale","new_value":"75","old_value":"70"}"#,
673 r#"{"type":"plugin","event_type":"changed","plugin":"example"}"#,
674 ];
675
676 for fixture in fixtures {
677 let event: EventKind = serde_json::from_str(fixture).unwrap();
678 assert_eq!(serde_json::to_string(&event).unwrap(), fixture);
679 }
680
681 let ordered: EventKind =
682 serde_json::from_str(r#"{"type":"army_arrived","army":1,"territory":3}"#).unwrap();
683 let reordered: EventKind =
684 serde_json::from_str(r#"{"territory":3,"army":1,"type":"army_arrived"}"#).unwrap();
685 assert_eq!(ordered, reordered);
686
687 let plugin_with_extra: EventKind = serde_json::from_str(
688 r#"{"type":"plugin","plugin":"example","event_type":"changed","extra":true}"#,
689 )
690 .unwrap();
691 assert_eq!(plugin_with_extra.plugin_identity(), None);
692 }
693}