use std::collections::BTreeMap;
use std::fmt;
use std::str::FromStr;
use std::time::SystemTime;
use serde::{Deserialize, Serialize};
macro_rules! wire_enum {
($(#[$meta:meta])* $name:ident { $($(#[$vmeta:meta])* $variant:ident = $wire:literal),+ $(,)? }) => {
$(#[$meta])*
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum $name {
$($(#[$vmeta])* $variant),+
}
impl $name {
pub const ALL: &'static [$name] = &[$($name::$variant),+];
pub fn as_str(self) -> &'static str {
match self {
$($name::$variant => $wire),+
}
}
}
impl fmt::Display for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
};
}
wire_enum! {
Severity {
Info = "info",
Success = "success",
Warning = "warning",
Error = "error",
Critical = "critical",
}
}
impl Severity {
pub const LIGHT: Severity = Severity::Info;
pub const BEEP: Severity = Severity::Success;
pub const LOUD: Severity = Severity::Warning;
pub const LONG: Severity = Severity::Error;
pub const BLAST: Severity = Severity::Critical;
pub fn horn(self) -> &'static str {
match self {
Severity::Info => "light",
Severity::Success => "beep",
Severity::Warning => "loud",
Severity::Error => "long",
Severity::Critical => "blast",
}
}
pub fn parse(value: &str) -> Option<Severity> {
let v = value.trim().to_ascii_lowercase();
Severity::ALL
.iter()
.copied()
.find(|s| s.as_str() == v || s.horn() == v)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseEnumError(pub(crate) String);
impl fmt::Display for ParseEnumError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl std::error::Error for ParseEnumError {}
impl FromStr for Severity {
type Err = ParseEnumError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Severity::parse(s).ok_or_else(|| {
ParseEnumError(format!(
"unknown severity {s:?}: use light (info), beep (success), loud (warning), long (error) or blast (critical)"
))
})
}
}
wire_enum! {
Priority {
Low = "low",
Normal = "normal",
High = "high",
Urgent = "urgent",
}
}
wire_enum! {
EventType {
Event = "event",
Problem = "problem",
Recovery = "recovery",
}
}
wire_enum! {
Category {
Infrastructure = "infrastructure",
Security = "security",
Backups = "backups",
Deployments = "deployments",
Payments = "payments",
Customers = "customers",
Sales = "sales",
Automation = "automation",
Personal = "personal",
Other = "other",
}
}
macro_rules! from_str_enum {
($name:ident, $what:literal) => {
impl FromStr for $name {
type Err = ParseEnumError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let v = s.trim().to_ascii_lowercase();
$name::ALL
.iter()
.copied()
.find(|x| x.as_str() == v)
.ok_or_else(|| {
let names: Vec<&str> = $name::ALL.iter().map(|x| x.as_str()).collect();
ParseEnumError(format!(
"unknown {} {s:?}: use one of {}",
$what,
names.join(", ")
))
})
}
}
};
}
from_str_enum!(Priority, "priority");
from_str_enum!(EventType, "event type");
from_str_enum!(Category, "category");
#[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(untagged)]
pub enum MetadataValue {
String(String),
Integer(i64),
Float(f64),
Bool(bool),
}
macro_rules! metadata_from {
($($t:ty => $variant:ident $(as $cast:ty)?),+ $(,)?) => {
$(impl From<$t> for MetadataValue {
fn from(v: $t) -> Self {
MetadataValue::$variant(v $(as $cast)?)
}
})+
};
}
metadata_from! {
i64 => Integer, i32 => Integer as i64, i16 => Integer as i64, i8 => Integer as i64,
u32 => Integer as i64, u16 => Integer as i64, u8 => Integer as i64,
f64 => Float, f32 => Float as f64, bool => Bool, String => String,
}
impl From<&str> for MetadataValue {
fn from(v: &str) -> Self {
MetadataValue::String(v.to_owned())
}
}
impl From<&String> for MetadataValue {
fn from(v: &String) -> Self {
MetadataValue::String(v.clone())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
#[non_exhaustive]
pub struct Action {
pub title: String,
pub url: String,
}
impl Action {
pub fn new(title: impl Into<String>, url: impl Into<String>) -> Self {
Action {
title: title.into(),
url: url.into(),
}
}
}
#[derive(Debug, Clone, PartialEq, Default)]
#[non_exhaustive]
pub struct Message {
pub message: String,
pub title: Option<String>,
pub severity: Option<Severity>,
pub priority: Option<Priority>,
pub category: Option<Category>,
pub source: Option<String>,
pub environment: Option<String>,
pub channel: Option<String>,
pub group_key: Option<String>,
pub event_type: Option<EventType>,
pub occurred_at: Option<SystemTime>,
pub url: Option<String>,
pub image_url: Option<String>,
pub actions: Vec<Action>,
pub metadata: BTreeMap<String, MetadataValue>,
pub ttl_seconds: Option<u32>,
pub source_sequence: Option<u64>,
}
impl Message {
pub fn new(message: impl Into<String>) -> Self {
Message {
message: message.into(),
..Message::default()
}
}
pub fn title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self
}
pub fn severity(mut self, severity: Severity) -> Self {
self.severity = Some(severity);
self
}
pub fn priority(mut self, priority: Priority) -> Self {
self.priority = Some(priority);
self
}
pub fn category(mut self, category: Category) -> Self {
self.category = Some(category);
self
}
pub fn source(mut self, source: impl Into<String>) -> Self {
self.source = Some(source.into());
self
}
pub fn environment(mut self, environment: impl Into<String>) -> Self {
self.environment = Some(environment.into());
self
}
pub fn channel(mut self, channel: impl Into<String>) -> Self {
self.channel = Some(channel.into());
self
}
pub fn group_key(mut self, group_key: impl Into<String>) -> Self {
self.group_key = Some(group_key.into());
self
}
pub fn event_type(mut self, event_type: EventType) -> Self {
self.event_type = Some(event_type);
self
}
pub fn occurred_at(mut self, occurred_at: SystemTime) -> Self {
self.occurred_at = Some(occurred_at);
self
}
pub fn url(mut self, url: impl Into<String>) -> Self {
self.url = Some(url.into());
self
}
pub fn image_url(mut self, image_url: impl Into<String>) -> Self {
self.image_url = Some(image_url.into());
self
}
pub fn action(mut self, title: impl Into<String>, url: impl Into<String>) -> Self {
self.actions.push(Action::new(title, url));
self
}
pub fn metadata(mut self, key: impl Into<String>, value: impl Into<MetadataValue>) -> Self {
self.metadata.insert(key.into(), value.into());
self
}
pub fn ttl_seconds(mut self, ttl_seconds: u32) -> Self {
self.ttl_seconds = Some(ttl_seconds);
self
}
pub fn source_sequence(mut self, source_sequence: u64) -> Self {
self.source_sequence = Some(source_sequence);
self
}
}
impl From<&str> for Message {
fn from(message: &str) -> Self {
Message::new(message)
}
}
impl From<String> for Message {
fn from(message: String) -> Self {
Message::new(message)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[non_exhaustive]
pub struct Defaults {
pub source: Option<String>,
pub environment: Option<String>,
pub channel: Option<String>,
}
impl Defaults {
pub fn new() -> Self {
Defaults::default()
}
pub fn source(mut self, source: impl Into<String>) -> Self {
self.source = Some(source.into());
self
}
pub fn environment(mut self, environment: impl Into<String>) -> Self {
self.environment = Some(environment.into());
self
}
pub fn channel(mut self, channel: impl Into<String>) -> Self {
self.channel = Some(channel.into());
self
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[non_exhaustive]
pub struct Accepted {
pub id: String,
#[serde(default)]
pub duplicate: bool,
#[serde(default)]
pub received_at: String,
}
impl Accepted {
pub fn received_at_time(&self) -> Option<SystemTime> {
crate::time::parse_rfc3339(&self.received_at)
}
}