use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;
macro_rules! branded_bytes {
($(#[$doc:meta])* $name:ident) => {
$(#[$doc])*
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct $name([u8; 16]);
impl $name {
pub fn generate() -> Self {
let mut bytes = [0u8; 16];
getrandom::fill(&mut bytes).expect("system entropy");
Self(bytes)
}
pub const fn from_bytes(bytes: [u8; 16]) -> Self {
Self(bytes)
}
pub const fn as_bytes(&self) -> &[u8; 16] {
&self.0
}
pub fn to_hex(&self) -> String {
hex::encode(self.0)
}
pub fn from_hex(text: &str) -> Result<Self, String> {
let bytes = hex::decode(text).map_err(|err| err.to_string())?;
let arr: [u8; 16] = bytes
.try_into()
.map_err(|_| "expected 16 bytes".to_owned())?;
Ok(Self(arr))
}
}
impl Serialize for $name {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&self.to_hex())
}
}
impl<'de> Deserialize<'de> for $name {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let text = String::deserialize(deserializer)?;
Self::from_hex(&text).map_err(serde::de::Error::custom)
}
}
impl fmt::Display for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.to_hex())
}
}
impl fmt::Debug for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}({})", stringify!($name), self.to_hex())
}
}
};
}
macro_rules! branded_u64 {
($(#[$doc:meta])* $name:ident) => {
$(#[$doc])*
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, Debug)]
pub struct $name(u64);
impl $name {
pub const fn new(value: u64) -> Self {
Self(value)
}
pub const fn get(self) -> u64 {
self.0
}
pub const fn saturating_add(self, n: u64) -> Self {
Self(self.0.saturating_add(n))
}
pub const fn next(self) -> Self {
Self(self.0.saturating_add(1))
}
}
impl fmt::Display for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
};
}
branded_bytes!(
MemberId
);
branded_bytes!(
ClusterId
);
branded_bytes!(
RunId
);
branded_bytes!(
ScopeId
);
branded_bytes!(
ActivationId
);
branded_bytes!(
WorkerSessionId
);
branded_bytes!(
WaitId
);
branded_bytes!(
CommandId
);
branded_bytes!(
EventId
);
branded_bytes!(
EffectKey
);
branded_bytes!(
ArtifactId
);
branded_u64!(
RaftTerm
);
branded_u64!(
OwnerGeneration
);
branded_u64!(
AttemptNo
);
branded_u64!(
RunSequence
);
branded_u64!(
LeaseRevision
);
branded_u64!(
WaitRevision
);
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ExecutionRole {
#[default]
Forward,
Compensation,
Reconciliation,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct DefinitionId {
pub name: String,
pub version: u32,
}
impl DefinitionId {
pub fn new(name: impl Into<String>, version: u32) -> Self {
Self {
name: name.into(),
version,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ActivityKey {
pub name: String,
pub version: u32,
}
impl ActivityKey {
pub fn new(name: impl Into<String>, version: u32) -> Self {
Self {
name: name.into(),
version,
}
}
pub fn as_stable_name(&self) -> String {
format!("{}/v{}", self.name, self.version)
}
pub fn parse(raw: &str) -> std::result::Result<Self, String> {
let (name, version) = raw
.rsplit_once("/v")
.ok_or_else(|| format!("activity {raw} must look like name/vN"))?;
let version: u32 = version
.parse()
.map_err(|_| format!("activity {raw} has a non-integer version"))?;
Ok(Self::new(name, version))
}
}
impl Serialize for ActivityKey {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&self.as_stable_name())
}
}
impl<'de> Deserialize<'de> for ActivityKey {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let text = String::deserialize(deserializer)?;
ActivityKey::parse(&text).map_err(serde::de::Error::custom)
}
}
impl fmt::Display for ActivityKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.as_stable_name())
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct NodeKey(pub String);
impl NodeKey {
pub fn parse(raw: &str) -> Result<Self, String> {
if raw.len() > 64 {
return Err("node key longer than 64 characters".to_owned());
}
let mut chars = raw.chars();
match chars.next() {
Some('a'..='z') => {}
_ => return Err(format!("invalid node key {raw}")),
}
if !chars.all(|ch| matches!(ch, 'a'..='z' | '0'..='9' | '_')) {
return Err(format!("invalid node key {raw}"));
}
Ok(Self(raw.to_owned()))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl fmt::Display for NodeKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
pub fn valid_ascii_name(raw: &str) -> bool {
!raw.is_empty()
&& raw.len() <= 64
&& raw.is_ascii()
&& raw
.chars()
.all(|ch| ch.is_ascii_alphanumeric() || matches!(ch, '_' | '.' | '-'))
}