bc_envelope/extension/expressions/
event.rs

1use anyhow::{ Error, Result };
2use bc_components::{ tags, ARID };
3use dcbor::prelude::*;
4use dcbor::Date;
5
6use crate::{ known_values, Envelope, EnvelopeEncodable };
7
8/// An `Event` represents a notification or message that doesn't expect a response.
9///
10/// Unlike `Request` and `Response` which form a pair, an `Event` is a standalone
11/// message that can be used for broadcasting information, logging, or publishing
12/// notifications. Events are used when the sender does not expect or require
13/// a response from the recipients.
14///
15/// Each event contains:
16/// - Content of a generic type `T` that holds the event payload
17/// - A unique identifier (ARID) for tracking and correlation
18/// - Optional metadata like a note and timestamp
19///
20/// When serialized to an envelope, events are tagged with `#6.40012` (TAG_EVENT).
21///
22/// # Type Parameters
23///
24/// * `T` - The type of content this event carries. Must implement `EnvelopeEncodable`
25///   and be convertible from an `Envelope`.
26///
27/// # Examples
28///
29/// ```
30/// use bc_envelope::prelude::*;
31/// use bc_components::ARID;
32/// use dcbor::Date;
33///
34/// // Create a status update event
35/// let event_id = ARID::new();
36/// let timestamp = Date::try_from("2024-08-15T13:45:30Z").unwrap();
37///
38/// let status_event = Event::<String>::new("System online", event_id)
39///     .with_note("Regular status update")
40///     .with_date(&timestamp);
41///
42/// // Convert to an envelope for transmission
43/// let envelope = status_event.into_envelope();
44/// ```
45#[derive(Debug, Clone, PartialEq)]
46pub struct Event<T>
47    where T: EnvelopeEncodable + TryFrom<Envelope> + std::fmt::Debug + Clone + PartialEq {
48    content: T,
49    id: ARID,
50    note: String,
51    date: Option<Date>,
52}
53
54impl<T> std::fmt::Display
55    for Event<T>
56    where T: EnvelopeEncodable + TryFrom<Envelope> + std::fmt::Debug + Clone + PartialEq
57{
58    /// Formats the event for display, showing its ID and content.
59    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60        write!(f, "Event({})", self.summary())
61    }
62}
63
64impl<T> Event<T>
65    where T: EnvelopeEncodable + TryFrom<Envelope> + std::fmt::Debug + Clone + PartialEq
66{
67    /// Returns a human-readable summary of the event.
68    pub fn summary(&self) -> String {
69        format!(
70            "id: {}, content: {}",
71            self.id.short_description(),
72            self.content.to_envelope().format_flat()
73        )
74    }
75}
76
77impl<T> Event<T>
78    where T: EnvelopeEncodable + TryFrom<Envelope> + std::fmt::Debug + Clone + PartialEq
79{
80    /// Creates a new event with the specified content and ID.
81    ///
82    /// # Arguments
83    ///
84    /// * `content` - The payload for this event
85    /// * `id` - Unique identifier for the event
86    ///
87    /// # Examples
88    ///
89    /// ```
90    /// use bc_envelope::prelude::*;
91    /// use bc_components::ARID;
92    ///
93    /// let event_id = ARID::new();
94    /// let event = Event::<String>::new("Temperature alert: 90°F", event_id);
95    /// ```
96    pub fn new(content: impl Into<T>, id: ARID) -> Self {
97        Self {
98            content: content.into(),
99            id,
100            note: String::new(),
101            date: None,
102        }
103    }
104}
105
106/// Trait that defines the behavior of an event.
107///
108/// This trait provides methods for composing events with metadata
109/// and for extracting information from events.
110pub trait EventBehavior<T> where T: EnvelopeEncodable + TryFrom<Envelope> {
111    //
112    // Composition
113    //
114
115    /// Adds a note to the event.
116    ///
117    /// This provides human-readable context about the event's purpose.
118    fn with_note(self, note: impl Into<String>) -> Self;
119
120    /// Adds a date to the event.
121    ///
122    /// This timestamp typically represents when the event occurred.
123    fn with_date(self, date: impl AsRef<Date>) -> Self;
124
125    //
126    // Parsing
127    //
128
129    /// Returns the content of the event.
130    fn content(&self) -> &T;
131
132    /// Returns the unique identifier (ARID) of the event.
133    fn id(&self) -> ARID;
134
135    /// Returns the note attached to the event, or an empty string if none exists.
136    fn note(&self) -> &str;
137
138    /// Returns the date attached to the event, if any.
139    fn date(&self) -> Option<&Date>;
140}
141
142impl<T> EventBehavior<T>
143    for Event<T>
144    where T: EnvelopeEncodable + TryFrom<Envelope> + std::fmt::Debug + Clone + PartialEq
145{
146    /// Adds a note to the event.
147    fn with_note(mut self, note: impl Into<String>) -> Self {
148        self.note = note.into();
149        self
150    }
151
152    /// Adds a date to the event.
153    fn with_date(mut self, date: impl AsRef<Date>) -> Self {
154        self.date = Some(date.as_ref().clone());
155        self
156    }
157
158    /// Returns the content of the event.
159    fn content(&self) -> &T {
160        &self.content
161    }
162
163    /// Returns the ID of the event.
164    fn id(&self) -> ARID {
165        self.id
166    }
167
168    /// Returns the note of the event.
169    fn note(&self) -> &str {
170        &self.note
171    }
172
173    /// Returns the date of the event.
174    fn date(&self) -> Option<&Date> {
175        self.date.as_ref()
176    }
177}
178
179/// Converts an `Event<T>` to an `Envelope`.
180///
181/// The envelope's subject is the event's ID tagged with TAG_EVENT,
182/// and assertions include the event's content, note (if not empty), and date (if present).
183impl<T> From<Event<T>>
184    for Envelope
185    where T: EnvelopeEncodable + TryFrom<Envelope> + std::fmt::Debug + Clone + PartialEq
186{
187    fn from(event: Event<T>) -> Self {
188        Envelope::new(CBOR::to_tagged_value(tags::TAG_EVENT, event.id))
189            .add_assertion(known_values::CONTENT, event.content.to_envelope())
190            .add_assertion_if(!event.note.is_empty(), known_values::NOTE, event.note)
191            .add_optional_assertion(known_values::DATE, event.date)
192    }
193}
194
195/// Converts an `Envelope` to an `Event<T>`.
196///
197/// This constructor extracts the event ID, content, note, and date from an envelope.
198/// The content is converted to the generic type `T`.
199impl<T> TryFrom<Envelope>
200    for Event<T>
201    where T: EnvelopeEncodable + TryFrom<Envelope> + std::fmt::Debug + Clone + PartialEq
202{
203    type Error = Error;
204
205    fn try_from(envelope: Envelope) -> Result<Self> {
206        let content_envelope = envelope.object_for_predicate(known_values::CONTENT)?;
207        let content = T::try_from(content_envelope).map_err(|_|
208            Error::msg("Failed to parse content")
209        )?;
210        Ok(Self {
211            content,
212            id: envelope
213                .subject()
214                .try_leaf()?
215                .try_into_expected_tagged_value(tags::TAG_EVENT)?
216                .try_into()?,
217            note: envelope
218                .extract_optional_object_for_predicate(known_values::NOTE)?
219                .unwrap_or_default(),
220            date: envelope.extract_optional_object_for_predicate(known_values::DATE)?,
221        })
222    }
223}
224
225#[cfg(test)]
226mod tests {
227    use super::*;
228    use hex_literal::hex;
229    use indoc::indoc;
230
231    fn request_id() -> ARID {
232        ARID::from_data(hex!("c66be27dbad7cd095ca77647406d07976dc0f35f0d4d654bb0e96dd227a1e9fc"))
233    }
234
235    #[test]
236    fn test_event() {
237        crate::register_tags();
238
239        let event_date = Date::try_from("2024-07-04T11:11:11Z").unwrap();
240        let event = Event::<String>
241            ::new("test", request_id())
242            .with_note("This is a test")
243            .with_date(&event_date);
244
245        let envelope: Envelope = event.clone().into();
246        #[rustfmt::skip]
247        let expected = indoc!{r#"
248            event(ARID(c66be27d)) [
249                'content': "test"
250                'date': 2024-07-04T11:11:11Z
251                'note': "This is a test"
252            ]
253        "#}.trim();
254        assert_eq!(envelope.format(), expected);
255
256        let parsed_request = Event::<String>::try_from(envelope).unwrap();
257        assert_eq!(parsed_request.content(), "test");
258        assert_eq!(parsed_request.note(), "This is a test");
259        assert_eq!(parsed_request.date(), Some(&event_date));
260    }
261}