use content_addressable::ContentId;
use serde::{Deserialize, Serialize};
use crate::error::ProtocolError;
macro_rules! content_id_newtype {
($(#[$meta:meta])* $name:ident, $kind:literal) => {
$(#[$meta])*
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(transparent)]
pub struct $name(
#[cfg_attr(feature = "schema", schemars(with = "String"))] ContentId,
);
impl $name {
#[must_use]
pub(crate) fn from_content_id(id: ContentId) -> Self {
Self(id)
}
#[must_use]
pub fn content_id(&self) -> &ContentId {
&self.0
}
pub fn parse(text: &str) -> Result<Self, ProtocolError> {
let id: ContentId = text.parse().map_err(|_| ProtocolError::InvalidId {
kind: $kind,
reason: format!("`{text}` is not a content id"),
})?;
if id.to_string() != text {
return Err(ProtocolError::NonCanonicalId {
presented: text.to_string(),
});
}
Ok(Self(id))
}
}
impl std::fmt::Display for $name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
};
}
content_id_newtype!(
DefinitionId,
"definition id"
);
content_id_newtype!(
InstanceId,
"instance id"
);
content_id_newtype!(
ResponseId,
"response id"
);
fn validated_name(kind: &'static str, name: String) -> Result<String, ProtocolError> {
if name.is_empty() {
return Err(ProtocolError::InvalidId {
kind,
reason: "must not be empty".to_string(),
});
}
if !name
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')
{
return Err(ProtocolError::InvalidId {
kind,
reason: format!("`{name}` has characters outside [A-Za-z0-9_-]"),
});
}
Ok(name)
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(into = "String", try_from = "String")]
pub struct ControlId(String);
#[cfg(feature = "schema")]
crate::string_scalar_schema!(ControlId, Some("^[A-Za-z0-9_-]+$"));
impl From<ControlId> for String {
fn from(value: ControlId) -> Self {
value.0
}
}
impl TryFrom<String> for ControlId {
type Error = ProtocolError;
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::new(value)
}
}
impl ControlId {
pub fn new(name: impl Into<String>) -> Result<Self, ProtocolError> {
Ok(Self(validated_name("control id", name.into())?))
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(into = "String", try_from = "String")]
pub struct OptionId(String);
#[cfg(feature = "schema")]
crate::string_scalar_schema!(OptionId, Some("^[A-Za-z0-9_-]+$"));
impl From<OptionId> for String {
fn from(value: OptionId) -> Self {
value.0
}
}
impl TryFrom<String> for OptionId {
type Error = ProtocolError;
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::new(value)
}
}
impl OptionId {
pub fn new(name: impl Into<String>) -> Result<Self, ProtocolError> {
Ok(Self(validated_name("option id", name.into())?))
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(into = "String", try_from = "String")]
pub struct Nonce(String);
#[cfg(feature = "schema")]
crate::string_scalar_schema!(Nonce, None::<&str>);
impl From<Nonce> for String {
fn from(value: Nonce) -> Self {
value.0
}
}
impl TryFrom<String> for Nonce {
type Error = ProtocolError;
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::new(value)
}
}
impl Nonce {
pub fn new(value: impl Into<String>) -> Result<Self, ProtocolError> {
let value = value.into();
if value.is_empty() {
return Err(ProtocolError::InvalidId {
kind: "nonce",
reason: "must not be empty".to_string(),
});
}
Ok(Self(value))
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[derive(
Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, Default,
)]
#[serde(transparent)]
pub struct Revision(u64);
impl Revision {
pub const FIRST: Self = Self(0);
#[must_use]
pub fn new(value: u64) -> Self {
Self(value)
}
#[must_use]
pub fn next(self) -> Self {
Self(self.0.saturating_add(1))
}
#[must_use]
pub fn get(self) -> u64 {
self.0
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(into = "String", try_from = "String")]
pub struct IdempotencyKey(String);
#[cfg(feature = "schema")]
crate::string_scalar_schema!(IdempotencyKey, None::<&str>);
impl From<IdempotencyKey> for String {
fn from(value: IdempotencyKey) -> Self {
value.0
}
}
impl TryFrom<String> for IdempotencyKey {
type Error = ProtocolError;
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::new(value)
}
}
impl IdempotencyKey {
pub fn new(value: impl Into<String>) -> Result<Self, ProtocolError> {
let value = value.into();
if value.is_empty() {
return Err(ProtocolError::InvalidId {
kind: "idempotency key",
reason: "must not be empty".to_string(),
});
}
Ok(Self(value))
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}