use core::fmt;
use core::str::FromStr;
use std::borrow::Cow;
use schemars::{JsonSchema, Schema, SchemaGenerator, json_schema};
use serde::de::{DeserializeOwned, Error as _};
use serde::{Deserialize, Deserializer, Serialize};
use serde_json::Value;
use crate::ids::{EventId, PrintId};
use crate::image::ImageRef;
use crate::raw::RawBytes;
use crate::timestamp::Timestamp;
pub const KIND_PATTERN: &str = "^[a-z][a-z0-9_]*$";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EventKindError {
name: String,
}
impl EventKindError {
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
}
impl fmt::Display for EventKindError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
formatter,
"{:?} is not a kind name: a kind is lowercase snake_case, matching {KIND_PATTERN}",
self.name
)
}
}
impl core::error::Error for EventKindError {}
fn is_kind_name(text: &str) -> bool {
let mut characters = text.chars();
characters
.next()
.is_some_and(|first| first.is_ascii_lowercase())
&& characters.all(|rest| rest.is_ascii_lowercase() || rest.is_ascii_digit() || rest == '_')
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
#[serde(transparent)]
pub struct EventKind(String);
impl EventKind {
pub fn new(name: impl Into<String>) -> Result<Self, EventKindError> {
let name = name.into();
if is_kind_name(&name) {
Ok(Self(name))
} else {
Err(EventKindError { name })
}
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl FromStr for EventKind {
type Err = EventKindError;
fn from_str(text: &str) -> Result<Self, Self::Err> {
Self::new(text)
}
}
impl<'de> Deserialize<'de> for EventKind {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let text = String::deserialize(deserializer)?;
Self::new(text).map_err(D::Error::custom)
}
}
impl fmt::Display for EventKind {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(&self.0)
}
}
impl JsonSchema for EventKind {
fn schema_name() -> Cow<'static, str> {
Cow::Borrowed("EventKind")
}
fn json_schema(_generator: &mut SchemaGenerator) -> Schema {
json_schema!({
"type": "string",
"title": "EventKind",
"description": "The name one kind of event is written down under: lowercase `snake_case`, declared by the domain that owns the event.",
"pattern": KIND_PATTERN
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct EventSource(String);
impl EventSource {
#[must_use]
pub fn new(name: impl Into<String>) -> Self {
Self(name.into())
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl fmt::Display for EventSource {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(&self.0)
}
}
impl JsonSchema for EventSource {
fn schema_name() -> Cow<'static, str> {
Cow::Borrowed("EventSource")
}
fn json_schema(_generator: &mut SchemaGenerator) -> Schema {
json_schema!({
"type": "string",
"title": "EventSource",
"description": "Where an event came from, as the bare string the domain that raised it declares for itself."
})
}
}
pub trait EventPayload: Serialize + DeserializeOwned + JsonSchema {
const KIND: &'static str;
#[must_use]
fn kind() -> EventKind {
EventKind::new(Self::KIND).expect("a declared kind name is lowercase snake_case")
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct EventBody {
pub kind: EventKind,
pub payload: Value,
}
impl EventBody {
pub fn of<P: EventPayload>(payload: &P) -> Result<Self, serde_json::Error> {
Ok(Self {
kind: P::kind(),
payload: serde_json::to_value(payload)?,
})
}
#[must_use]
pub fn is<P: EventPayload>(&self) -> bool {
self.kind.as_str() == P::KIND
}
#[must_use]
pub fn read<P: EventPayload>(&self) -> Option<Result<P, serde_json::Error>> {
self.is::<P>()
.then(|| serde_json::from_value(self.payload.clone()))
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct EventRecord {
pub id: EventId,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub print_id: Option<PrintId>,
pub source: EventSource,
pub received_at: Timestamp,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub image: Option<ImageRef>,
#[serde(flatten)]
pub body: EventBody,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub raw: Option<RawBytes>,
}
impl EventRecord {
#[must_use]
pub const fn kind(&self) -> &EventKind {
&self.body.kind
}
#[must_use]
pub fn payload_as<P: EventPayload>(&self) -> Option<Result<P, serde_json::Error>> {
self.body.read::<P>()
}
}