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