1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4use std::error::Error;
5use std::fmt::{Display, Formatter};
6
7#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
8pub struct OwnableEventSource {
10 pub id: String,
11 pub owner: String,
12 pub issuer: String,
13}
14
15#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
16#[serde(rename_all = "camelCase")]
17pub struct OwnableEvent {
19 pub source: OwnableEventSource,
20 pub event_type: String,
21 pub attributes: Value,
22}
23
24#[derive(Clone, Debug, PartialEq, Eq)]
25pub enum OwnableEventError {
26 UnexpectedEventType {
27 expected: &'static str,
28 actual: String,
29 },
30}
31
32impl Display for OwnableEventError {
33 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
34 match self {
35 OwnableEventError::UnexpectedEventType { expected, actual } => {
36 write!(
37 f,
38 "unexpected ownable event type: expected '{expected}', got '{actual}'"
39 )
40 }
41 }
42 }
43}
44
45impl Error for OwnableEventError {}
46
47pub fn require_ownable_event_type(
48 event: &OwnableEvent,
49 expected: &'static str,
50) -> Result<(), OwnableEventError> {
51 if event.event_type == expected {
52 Ok(())
53 } else {
54 Err(OwnableEventError::UnexpectedEventType {
55 expected,
56 actual: event.event_type.clone(),
57 })
58 }
59}
60
61pub fn source_matches(event: &OwnableEvent, id: &str, owner: &str, issuer: &str) -> bool {
62 event.source.id == id && event.source.owner == owner && event.source.issuer == issuer
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68 use serde_json::json;
69
70 #[test]
71 fn ownable_event_cbor_round_trip_preserves_source_and_attributes() {
72 let event = OwnableEvent {
73 source: OwnableEventSource {
74 id: "ownable-123".to_string(),
75 owner: "owner-1".to_string(),
76 issuer: "issuer-1".to_string(),
77 },
78 event_type: "consume".to_string(),
79 attributes: json!({"consumerId": "abc", "amount": 1}),
80 };
81
82 let encoded = crate::abi::cbor_to_vec(&event).expect("serialize ownable event");
83 let decoded: OwnableEvent =
84 crate::abi::cbor_from_slice(&encoded).expect("deserialize ownable event");
85
86 assert_eq!(decoded, event);
87 }
88
89 #[test]
90 fn require_ownable_event_type_accepts_matching_type() {
91 let event = OwnableEvent {
92 source: OwnableEventSource {
93 id: "ownable-123".to_string(),
94 owner: "owner-1".to_string(),
95 issuer: "issuer-1".to_string(),
96 },
97 event_type: "consume".to_string(),
98 attributes: json!({}),
99 };
100
101 assert_eq!(require_ownable_event_type(&event, "consume"), Ok(()));
102 }
103
104 #[test]
105 fn require_ownable_event_type_rejects_wrong_type() {
106 let event = OwnableEvent {
107 source: OwnableEventSource {
108 id: "ownable-123".to_string(),
109 owner: "owner-1".to_string(),
110 issuer: "issuer-1".to_string(),
111 },
112 event_type: "redeem".to_string(),
113 attributes: json!({}),
114 };
115
116 assert_eq!(
117 require_ownable_event_type(&event, "consume"),
118 Err(OwnableEventError::UnexpectedEventType {
119 expected: "consume",
120 actual: "redeem".to_string(),
121 })
122 );
123 }
124
125 #[test]
126 fn source_matches_checks_all_identity_fields() {
127 let event = OwnableEvent {
128 source: OwnableEventSource {
129 id: "ownable-123".to_string(),
130 owner: "owner-1".to_string(),
131 issuer: "issuer-1".to_string(),
132 },
133 event_type: "consume".to_string(),
134 attributes: json!({}),
135 };
136
137 assert!(source_matches(&event, "ownable-123", "owner-1", "issuer-1"));
138 assert!(!source_matches(&event, "ownable-123", "owner-2", "issuer-1"));
139 }
140}