restate_email/
contract.rs1use email_message::OutboundMessage;
4use email_transport::{SendOptions, SendReport, TransportOptionRegistry, string_newtype};
5use serde::de::{DeserializeSeed, IgnoredAny, MapAccess, Visitor};
6use serde::{Deserialize, Serialize};
7
8string_newtype! {
9 @unchecked TransportKey
14}
15
16#[derive(Debug, Serialize)]
27#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
28#[cfg_attr(feature = "schemars", schemars(example = example_send_request()))]
29pub struct SendRequest {
30 pub transport: TransportKey,
32 pub message: OutboundMessage,
34 #[serde(default)]
36 #[cfg_attr(feature = "schemars", schemars(default))]
37 pub options: SendOptions,
38}
39
40pub struct SendRequestSeed<'a> {
45 registry: &'a TransportOptionRegistry,
46}
47
48impl<'a> SendRequestSeed<'a> {
49 #[must_use]
51 pub const fn new(registry: &'a TransportOptionRegistry) -> Self {
52 Self { registry }
53 }
54}
55
56impl<'de> DeserializeSeed<'de> for SendRequestSeed<'_> {
57 type Value = SendRequest;
58
59 fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
60 where
61 D: serde::Deserializer<'de>,
62 {
63 deserializer.deserialize_map(SendRequestVisitor {
64 registry: self.registry,
65 })
66 }
67}
68
69struct SendRequestVisitor<'a> {
70 registry: &'a TransportOptionRegistry,
71}
72
73#[derive(Deserialize)]
74#[serde(field_identifier, rename_all = "snake_case")]
75enum SendRequestField {
76 Transport,
77 Message,
78 Options,
79 #[serde(other)]
80 Other,
81}
82
83impl<'de> Visitor<'de> for SendRequestVisitor<'_> {
84 type Value = SendRequest;
85
86 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87 formatter.write_str("an email send request")
88 }
89
90 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
91 where
92 A: MapAccess<'de>,
93 {
94 let mut transport = None;
95 let mut message = None;
96 let mut options = None;
97
98 while let Some(field) = map.next_key::<SendRequestField>()? {
99 match field {
100 SendRequestField::Transport => {
101 if transport.is_some() {
102 return Err(serde::de::Error::duplicate_field("transport"));
103 }
104 transport = Some(map.next_value()?);
105 }
106 SendRequestField::Message => {
107 if message.is_some() {
108 return Err(serde::de::Error::duplicate_field("message"));
109 }
110 message = Some(map.next_value()?);
111 }
112 SendRequestField::Options => {
113 if options.is_some() {
114 return Err(serde::de::Error::duplicate_field("options"));
115 }
116 options = Some(
117 map.next_value_seed(
118 self.registry
119 .send_options_seed()
120 .ignore_unknown_transport_options(),
121 )?,
122 );
123 }
124 SendRequestField::Other => {
125 map.next_value::<IgnoredAny>()?;
126 }
127 }
128 }
129
130 Ok(SendRequest {
131 transport: transport.ok_or_else(|| serde::de::Error::missing_field("transport"))?,
132 message: message.ok_or_else(|| serde::de::Error::missing_field("message"))?,
133 options: options.unwrap_or_default(),
134 })
135 }
136}
137
138#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
140#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
141#[cfg_attr(feature = "schemars", schemars(example = example_send_response()))]
142#[non_exhaustive]
143pub struct SendResponse {
144 pub report: SendReport,
146}
147
148impl From<SendReport> for SendResponse {
149 fn from(report: SendReport) -> Self {
150 Self { report }
151 }
152}
153
154#[cfg(feature = "schemars")]
155fn example_send_response() -> serde_json::Value {
156 serde_json::json!({
157 "report": {
158 "provider": "your-provider",
159 "provider_message_id": "184fa9a3-f967-4a98-9d8f-57152e7cbe64",
160 "accepted": ["alice@example.com", "bob@example.com"],
161 },
162 })
163}
164
165#[cfg(feature = "schemars")]
166fn example_send_request() -> serde_json::Value {
167 serde_json::json!({
168 "transport": "your-transport",
169 "options": {
170 "idempotency_key": "foo",
171 "transport_options": {
172 "your-transport": {"tags": [{"name": "campaign", "value": "test"}]}
173 },
174 },
175 "message": {
176 "from": {"type": "mailbox", "name": "Alice", "email": "alice@example.com"},
177 "to": [{"type": "mailbox", "name": "Bob", "email": "bob@example.com"}],
178 "subject": "Test email",
179 "body": {"type": "text", "text": "Hello everyone! This is a test email."},
180 },
181 })
182}
183
184#[cfg(all(test, feature = "schemars"))]
185mod tests {
186 use email_transport::TransportOptionRegistry;
187 use serde::de::DeserializeSeed as _;
188
189 #[cfg(feature = "schemars")]
190 #[test]
191 fn schema_example_is_a_valid_send_request() {
192 let value = super::example_send_request();
193
194 super::SendRequestSeed::new(&TransportOptionRegistry::new())
195 .deserialize(value)
196 .expect("schema example should deserialize");
197 }
198}