1use std::collections::BTreeMap;
2use std::fmt;
3use std::str::FromStr;
4use std::time::SystemTime;
5
6use serde::{Deserialize, Serialize};
7
8macro_rules! wire_enum {
9 ($(#[$meta:meta])* $name:ident { $($(#[$vmeta:meta])* $variant:ident = $wire:literal),+ $(,)? }) => {
10 $(#[$meta])*
11 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
12 #[serde(rename_all = "lowercase")]
13 pub enum $name {
14 $($(#[$vmeta])* $variant),+
15 }
16
17 impl $name {
18 pub const ALL: &'static [$name] = &[$($name::$variant),+];
20
21 pub fn as_str(self) -> &'static str {
23 match self {
24 $($name::$variant => $wire),+
25 }
26 }
27 }
28
29 impl fmt::Display for $name {
30 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31 f.write_str(self.as_str())
32 }
33 }
34 };
35}
36
37wire_enum! {
38 Severity {
49 Info = "info",
51 Success = "success",
53 Warning = "warning",
55 Error = "error",
57 Critical = "critical",
59 }
60}
61
62impl Severity {
63 pub const LIGHT: Severity = Severity::Info;
65 pub const BEEP: Severity = Severity::Success;
67 pub const LOUD: Severity = Severity::Warning;
69 pub const LONG: Severity = Severity::Error;
71 pub const BLAST: Severity = Severity::Critical;
73
74 pub fn horn(self) -> &'static str {
76 match self {
77 Severity::Info => "light",
78 Severity::Success => "beep",
79 Severity::Warning => "loud",
80 Severity::Error => "long",
81 Severity::Critical => "blast",
82 }
83 }
84
85 pub fn parse(value: &str) -> Option<Severity> {
88 let v = value.trim().to_ascii_lowercase();
89 Severity::ALL
90 .iter()
91 .copied()
92 .find(|s| s.as_str() == v || s.horn() == v)
93 }
94}
95
96#[derive(Debug, Clone, PartialEq, Eq)]
98pub struct ParseEnumError(pub(crate) String);
99
100impl fmt::Display for ParseEnumError {
101 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102 f.write_str(&self.0)
103 }
104}
105
106impl std::error::Error for ParseEnumError {}
107
108impl FromStr for Severity {
109 type Err = ParseEnumError;
110
111 fn from_str(s: &str) -> Result<Self, Self::Err> {
112 Severity::parse(s).ok_or_else(|| {
113 ParseEnumError(format!(
114 "unknown severity {s:?}: use light (info), beep (success), loud (warning), long (error) or blast (critical)"
115 ))
116 })
117 }
118}
119
120wire_enum! {
121 Priority {
124 Low = "low",
126 Normal = "normal",
128 High = "high",
130 Urgent = "urgent",
132 }
133}
134
135wire_enum! {
136 EventType {
138 Event = "event",
140 Problem = "problem",
142 Recovery = "recovery",
144 }
145}
146
147wire_enum! {
148 Category {
150 Infrastructure = "infrastructure",
152 Security = "security",
154 Backups = "backups",
156 Deployments = "deployments",
158 Payments = "payments",
160 Customers = "customers",
162 Sales = "sales",
164 Automation = "automation",
166 Personal = "personal",
168 Other = "other",
170 }
171}
172
173macro_rules! from_str_enum {
174 ($name:ident, $what:literal) => {
175 impl FromStr for $name {
176 type Err = ParseEnumError;
177
178 fn from_str(s: &str) -> Result<Self, Self::Err> {
179 let v = s.trim().to_ascii_lowercase();
180 $name::ALL
181 .iter()
182 .copied()
183 .find(|x| x.as_str() == v)
184 .ok_or_else(|| {
185 let names: Vec<&str> = $name::ALL.iter().map(|x| x.as_str()).collect();
186 ParseEnumError(format!(
187 "unknown {} {s:?}: use one of {}",
188 $what,
189 names.join(", ")
190 ))
191 })
192 }
193 }
194 };
195}
196
197from_str_enum!(Priority, "priority");
198from_str_enum!(EventType, "event type");
199from_str_enum!(Category, "category");
200
201#[derive(Debug, Clone, PartialEq, Serialize)]
203#[serde(untagged)]
204pub enum MetadataValue {
205 String(String),
207 Integer(i64),
209 Float(f64),
211 Bool(bool),
213}
214
215macro_rules! metadata_from {
216 ($($t:ty => $variant:ident $(as $cast:ty)?),+ $(,)?) => {
217 $(impl From<$t> for MetadataValue {
218 fn from(v: $t) -> Self {
219 MetadataValue::$variant(v $(as $cast)?)
220 }
221 })+
222 };
223}
224
225metadata_from! {
226 i64 => Integer, i32 => Integer as i64, i16 => Integer as i64, i8 => Integer as i64,
227 u32 => Integer as i64, u16 => Integer as i64, u8 => Integer as i64,
228 f64 => Float, f32 => Float as f64, bool => Bool, String => String,
229}
230
231impl From<&str> for MetadataValue {
232 fn from(v: &str) -> Self {
233 MetadataValue::String(v.to_owned())
234 }
235}
236
237impl From<&String> for MetadataValue {
238 fn from(v: &String) -> Self {
239 MetadataValue::String(v.clone())
240 }
241}
242
243#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
254#[non_exhaustive]
255pub struct Action {
256 pub title: String,
258 pub url: String,
260}
261
262impl Action {
263 pub fn new(title: impl Into<String>, url: impl Into<String>) -> Self {
265 Action {
266 title: title.into(),
267 url: url.into(),
268 }
269 }
270}
271
272#[derive(Debug, Clone, PartialEq, Default)]
291#[non_exhaustive]
292pub struct Message {
293 pub message: String,
295 pub title: Option<String>,
297 pub severity: Option<Severity>,
299 pub priority: Option<Priority>,
301 pub category: Option<Category>,
303 pub source: Option<String>,
305 pub environment: Option<String>,
307 pub channel: Option<String>,
309 pub group_key: Option<String>,
313 pub event_type: Option<EventType>,
315 pub occurred_at: Option<SystemTime>,
317 pub url: Option<String>,
319 pub image_url: Option<String>,
322 pub actions: Vec<Action>,
324 pub metadata: BTreeMap<String, MetadataValue>,
326 pub ttl_seconds: Option<u32>,
328 pub source_sequence: Option<u64>,
331}
332
333impl Message {
334 pub fn new(message: impl Into<String>) -> Self {
336 Message {
337 message: message.into(),
338 ..Message::default()
339 }
340 }
341
342 pub fn title(mut self, title: impl Into<String>) -> Self {
344 self.title = Some(title.into());
345 self
346 }
347
348 pub fn severity(mut self, severity: Severity) -> Self {
350 self.severity = Some(severity);
351 self
352 }
353
354 pub fn priority(mut self, priority: Priority) -> Self {
356 self.priority = Some(priority);
357 self
358 }
359
360 pub fn category(mut self, category: Category) -> Self {
362 self.category = Some(category);
363 self
364 }
365
366 pub fn source(mut self, source: impl Into<String>) -> Self {
368 self.source = Some(source.into());
369 self
370 }
371
372 pub fn environment(mut self, environment: impl Into<String>) -> Self {
374 self.environment = Some(environment.into());
375 self
376 }
377
378 pub fn channel(mut self, channel: impl Into<String>) -> Self {
380 self.channel = Some(channel.into());
381 self
382 }
383
384 pub fn group_key(mut self, group_key: impl Into<String>) -> Self {
386 self.group_key = Some(group_key.into());
387 self
388 }
389
390 pub fn event_type(mut self, event_type: EventType) -> Self {
392 self.event_type = Some(event_type);
393 self
394 }
395
396 pub fn occurred_at(mut self, occurred_at: SystemTime) -> Self {
398 self.occurred_at = Some(occurred_at);
399 self
400 }
401
402 pub fn url(mut self, url: impl Into<String>) -> Self {
404 self.url = Some(url.into());
405 self
406 }
407
408 pub fn image_url(mut self, image_url: impl Into<String>) -> Self {
410 self.image_url = Some(image_url.into());
411 self
412 }
413
414 pub fn action(mut self, title: impl Into<String>, url: impl Into<String>) -> Self {
416 self.actions.push(Action::new(title, url));
417 self
418 }
419
420 pub fn metadata(mut self, key: impl Into<String>, value: impl Into<MetadataValue>) -> Self {
422 self.metadata.insert(key.into(), value.into());
423 self
424 }
425
426 pub fn ttl_seconds(mut self, ttl_seconds: u32) -> Self {
428 self.ttl_seconds = Some(ttl_seconds);
429 self
430 }
431
432 pub fn source_sequence(mut self, source_sequence: u64) -> Self {
434 self.source_sequence = Some(source_sequence);
435 self
436 }
437}
438
439impl From<&str> for Message {
440 fn from(message: &str) -> Self {
441 Message::new(message)
442 }
443}
444
445impl From<String> for Message {
446 fn from(message: String) -> Self {
447 Message::new(message)
448 }
449}
450
451#[derive(Debug, Clone, PartialEq, Eq, Default)]
453#[non_exhaustive]
454pub struct Defaults {
455 pub source: Option<String>,
457 pub environment: Option<String>,
459 pub channel: Option<String>,
461}
462
463impl Defaults {
464 pub fn new() -> Self {
466 Defaults::default()
467 }
468
469 pub fn source(mut self, source: impl Into<String>) -> Self {
471 self.source = Some(source.into());
472 self
473 }
474
475 pub fn environment(mut self, environment: impl Into<String>) -> Self {
477 self.environment = Some(environment.into());
478 self
479 }
480
481 pub fn channel(mut self, channel: impl Into<String>) -> Self {
483 self.channel = Some(channel.into());
484 self
485 }
486}
487
488#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
490#[non_exhaustive]
491pub struct Accepted {
492 pub id: String,
494 #[serde(default)]
497 pub duplicate: bool,
498 #[serde(default)]
500 pub received_at: String,
501}
502
503impl Accepted {
504 pub fn received_at_time(&self) -> Option<SystemTime> {
506 crate::time::parse_rfc3339(&self.received_at)
507 }
508}