use core::fmt;
use uuid::Uuid;
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum IdError {
Empty,
TooLong {
len: usize,
max: usize,
},
ControlCharacter,
}
impl fmt::Display for IdError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => f.write_str("value must not be empty"),
Self::TooLong { len, max } => {
write!(f, "value length {len} exceeds the maximum of {max}")
}
Self::ControlCharacter => f.write_str("value must not contain a control character"),
}
}
}
impl std::error::Error for IdError {}
pub(crate) fn contains_control_char(s: &str) -> bool {
s.chars().any(char::is_control)
}
#[macro_export]
macro_rules! uuid_id {
($(#[$meta:meta])* $name:ident in $krate:ident) => {
$crate::uuid_id!(@base $(#[$meta])* $name in $krate);
$crate::uuid_id!(@mint $name in $krate);
impl ::core::default::Default for $name {
fn default() -> Self {
Self::new()
}
}
};
($(#[$meta:meta])* $name:ident in $krate:ident, no_default) => {
$crate::uuid_id!(@base $(#[$meta])* $name in $krate);
$crate::uuid_id!(@mint $name in $krate);
};
($(#[$meta:meta])* $name:ident in $krate:ident, no_mint) => {
$crate::uuid_id!(@base $(#[$meta])* $name in $krate);
};
(@base $(#[$meta:meta])* $name:ident in $krate:ident) => {
$(#[$meta])*
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct $name($crate::uuid::Uuid);
impl $name {
#[doc = concat!("```\nuse ", stringify!($krate), "::", stringify!($name), ";")]
#[doc = concat!("assert_eq!(", stringify!($name), "::from_uuid(raw).as_uuid(), raw);")]
#[must_use]
pub const fn from_uuid(id: $crate::uuid::Uuid) -> Self {
Self(id)
}
#[doc = concat!("```\nuse ", stringify!($krate), "::", stringify!($name), ";")]
#[doc = concat!("assert_eq!(", stringify!($name), "::from_uuid(raw).as_uuid(), raw);")]
#[must_use]
pub const fn as_uuid(&self) -> $crate::uuid::Uuid {
self.0
}
}
impl ::core::fmt::Display for $name {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
::core::fmt::Display::fmt(&self.0, f)
}
}
};
(@mint $name:ident in $krate:ident) => {
#[allow(
clippy::new_without_default,
reason = "`Default` is opt-in: the bare `uuid_id!` form adds it, `no_default` does not"
)]
impl $name {
#[doc = concat!("```\nuse ", stringify!($krate), "::", stringify!($name), ";\n")]
#[doc = concat!("assert!(!", stringify!($name), "::new().as_uuid().is_nil());")]
#[must_use]
pub fn new() -> Self {
Self($crate::uuid::Uuid::now_v7())
}
}
};
}
#[macro_export]
macro_rules! uuid_id_serde {
($name:ident) => {
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
impl ::serde::Serialize for $name {
fn serialize<S: ::serde::Serializer>(
&self,
s: S,
) -> ::core::result::Result<S::Ok, S::Error> {
s.collect_str(&self.0)
}
}
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
impl<'de> ::serde::Deserialize<'de> for $name {
fn deserialize<D: ::serde::Deserializer<'de>>(
d: D,
) -> ::core::result::Result<Self, D::Error> {
let raw = <::std::string::String as ::serde::Deserialize>::deserialize(d)?;
$crate::uuid::Uuid::parse_str(&raw)
.map(Self)
.map_err(<D::Error as ::serde::de::Error>::custom)
}
}
};
}
uuid_id!(
MessageId in reliar_core
);
#[cfg(feature = "serde")]
uuid_id_serde!(MessageId);
uuid_id!(
ConversationId in reliar_core,
no_mint
);
#[cfg(feature = "serde")]
uuid_id_serde!(ConversationId);
uuid_id!(
RequestId in reliar_core,
no_mint
);
#[cfg(feature = "serde")]
uuid_id_serde!(RequestId);
impl ConversationId {
pub const UNSET: Self = Self::from_uuid(Uuid::nil());
#[must_use]
pub const fn is_unset(&self) -> bool {
self.0.is_nil()
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct CorrelationId(String);
impl CorrelationId {
pub const MAX_LEN: usize = 256;
pub fn parse(s: impl Into<String>) -> Result<Self, IdError> {
let s = s.into();
if s.is_empty() {
return Err(IdError::Empty);
}
if contains_control_char(&s) {
return Err(IdError::ControlCharacter);
}
if s.len() > Self::MAX_LEN {
return Err(IdError::TooLong {
len: s.len(),
max: Self::MAX_LEN,
});
}
Ok(Self(s))
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl fmt::Display for CorrelationId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
mod serde_impls {
use serde::{Deserialize, Serialize, de::Error as _};
use super::CorrelationId;
impl Serialize for CorrelationId {
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
s.collect_str(&self.0)
}
}
impl<'de> Deserialize<'de> for CorrelationId {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let raw = String::deserialize(d)?;
Self::parse(raw).map_err(D::Error::custom)
}
}
}