#[cfg(feature = "alloc")]
use alloc::{string::String, string::ToString};
use super::{DispatchError, DispatchResult};
macro_rules! general_id {
($name:ident, $kind:literal) => {
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct $name(String);
impl $name {
pub fn new(value: impl Into<String>) -> DispatchResult<Self> {
let value = value.into();
validate_general($kind, &value)?;
Ok(Self(value))
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl core::fmt::Display for $name {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
formatter.write_str(&self.0)
}
}
impl serde::Serialize for $name {
fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.0)
}
}
impl<'de> serde::Deserialize<'de> for $name {
fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = String::deserialize(deserializer)?;
Self::new(value).map_err(serde::de::Error::custom)
}
}
};
}
macro_rules! slug_id {
($name:ident, $kind:literal) => {
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct $name(String);
impl $name {
pub fn new(value: impl Into<String>) -> DispatchResult<Self> {
let value = value.into();
validate_slug($kind, &value)?;
Ok(Self(value))
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl core::fmt::Display for $name {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
formatter.write_str(&self.0)
}
}
impl serde::Serialize for $name {
fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.0)
}
}
impl<'de> serde::Deserialize<'de> for $name {
fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = String::deserialize(deserializer)?;
Self::new(value).map_err(serde::de::Error::custom)
}
}
};
}
general_id!(AgentId, "agent id");
general_id!(AgentType, "agent type");
general_id!(SessionId, "session id");
slug_id!(RunId, "run id");
slug_id!(LaneId, "lane id");
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct ProjectId(String);
impl ProjectId {
pub fn new(value: impl Into<String>) -> DispatchResult<Self> {
let value = value.into();
let bytes = value.as_bytes();
let valid = bytes.len() == 36
&& [8, 13, 18, 23]
.into_iter()
.all(|index| bytes[index] == b'-')
&& bytes[14] == b'7'
&& matches!(bytes[19], b'8' | b'9' | b'a' | b'b')
&& bytes.iter().enumerate().all(|(index, byte)| {
[8, 13, 18, 23].contains(&index)
|| byte.is_ascii_digit()
|| matches!(*byte, b'a'..=b'f')
});
if valid {
Ok(Self(value))
} else {
Err(DispatchError::InvalidIdentifier {
kind: "project id",
value,
})
}
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl core::fmt::Display for ProjectId {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
formatter.write_str(&self.0)
}
}
impl serde::Serialize for ProjectId {
fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.0)
}
}
impl<'de> serde::Deserialize<'de> for ProjectId {
fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = String::deserialize(deserializer)?;
Self::new(value).map_err(serde::de::Error::custom)
}
}
fn validate_general(kind: &'static str, value: &str) -> DispatchResult<()> {
let bytes = value.as_bytes();
let valid = (1..=128).contains(&bytes.len())
&& value != "."
&& value != ".."
&& bytes[0].is_ascii_alphanumeric()
&& bytes
.iter()
.all(|byte| byte.is_ascii_alphanumeric() || matches!(*byte, b'.' | b'_' | b':' | b'-'));
if valid {
Ok(())
} else {
Err(DispatchError::InvalidIdentifier {
kind,
value: value.to_string(),
})
}
}
fn validate_slug(kind: &'static str, value: &str) -> DispatchResult<()> {
let bytes = value.as_bytes();
let valid = (1..=64).contains(&bytes.len())
&& (bytes[0].is_ascii_lowercase() || bytes[0].is_ascii_digit())
&& bytes
.iter()
.all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit() || *byte == b'-');
if valid {
Ok(())
} else {
Err(DispatchError::InvalidIdentifier {
kind,
value: value.to_string(),
})
}
}