Skip to main content

tuwunel_core/matrix/pdu/
builder.rs

1use std::{collections::BTreeMap, fmt};
2
3use ruma::{
4	MilliSecondsSinceUnixEpoch, OwnedEventId,
5	events::{MessageLikeEventContent, StateEventContent, TimelineEventType},
6};
7use serde::Deserialize;
8use serde_json::value::{RawValue as RawJsonValue, to_raw_value};
9
10use super::{Content, StateKey};
11
12/// Collects the initial fields needed to build and append a PDU.
13///
14/// The timeline service supplies event graph fields, sender data, and hashes
15/// after this value is created. Constructors serialize typed Ruma event
16/// content.
17#[derive(Deserialize)]
18pub struct Builder {
19	/// Matrix event type for the new PDU.
20	///
21	/// Typed constructors derive this value from the supplied event content.
22	#[serde(rename = "type")]
23	pub event_type: TimelineEventType,
24
25	/// Raw canonical content for the new event.
26	///
27	/// The builder retains encoded content until the PDU is assembled and
28	/// signed.
29	pub content: Content,
30
31	/// Unsigned metadata to include with the new event.
32	///
33	/// Typical values include local transaction IDs and prior-state metadata.
34	pub unsigned: Option<BTreeMap<String, serde_json::Value>>,
35
36	/// State key for a state event.
37	///
38	/// A missing key identifies a message-like timeline event.
39	pub state_key: Option<StateKey>,
40
41	/// Event ID targeted by a redaction.
42	///
43	/// The value becomes the legacy top-level `redacts` field. Callers place a
44	/// content-based redaction target in the encoded content separately.
45	pub redacts: Option<OwnedEventId>,
46
47	/// Overrides the event timestamp for appservice messaging.
48	///
49	/// An absent value uses the current time when the PDU is built. Ordinary
50	/// callers should leave this field unset.
51	pub timestamp: Option<MilliSecondsSinceUnixEpoch>,
52}
53
54impl Default for Builder {
55	fn default() -> Self {
56		Self {
57			event_type: "m.room.message".into(),
58			content: Box::<RawJsonValue>::default().into(),
59			unsigned: None,
60			state_key: None,
61			redacts: None,
62			timestamp: None,
63		}
64	}
65}
66
67impl Builder {
68	/// Builds a state-event template from typed event content.
69	///
70	/// The content's event type and supplied state key populate the
71	/// corresponding builder fields. Remaining optional fields use their
72	/// defaults.
73	///
74	/// # Panics
75	///
76	/// Panics if the event content cannot be serialized as raw JSON.
77	pub fn state<S, T>(state_key: S, content: &T) -> Self
78	where
79		T: StateEventContent,
80		S: Into<StateKey>,
81	{
82		Self {
83			event_type: content.event_type().into(),
84			content: to_raw_value(content)
85				.map(Into::into)
86				.expect("Builder failed to serialize state event content to RawValue"),
87			state_key: Some(state_key.into()),
88			..Self::default()
89		}
90	}
91
92	/// Builds a message-like event template from typed event content.
93	///
94	/// The content's event type populates the builder and the state key remains
95	/// absent. Remaining optional fields use their defaults.
96	///
97	/// # Panics
98	///
99	/// Panics if the event content cannot be serialized as raw JSON.
100	pub fn timeline<T>(content: &T) -> Self
101	where
102		T: MessageLikeEventContent,
103	{
104		Self {
105			event_type: content.event_type().into(),
106			content: to_raw_value(content)
107				.map(Into::into)
108				.expect("Builder failed to serialize timeline event content to RawValue"),
109			..Self::default()
110		}
111	}
112}
113
114impl fmt::Debug for Builder {
115	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116		let mut d = f.debug_struct("Builder");
117
118		d.field("type", &self.event_type);
119
120		if let Some(state_key) = self.state_key.as_ref() {
121			d.field("state_key", state_key);
122		}
123
124		if let Some(redacts) = self.redacts.as_ref() {
125			d.field("redacts", redacts);
126		}
127
128		if let Some(timestamp) = self.timestamp.as_ref() {
129			d.field("ts", timestamp);
130		}
131
132		if let Some(unsigned) = self.unsigned.as_ref() {
133			d.field("unsigned", unsigned);
134		}
135
136		d.field("content", &self.content);
137
138		d.finish()
139	}
140}