Skip to main content

ownable_std/
ingest.rs

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)]
8/// Identity metadata for the source Ownable of a cross-ownable event.
9pub 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")]
17/// Contract-facing payload for a semantic event emitted by another Ownable.
18pub 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}