use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;
use thiserror::Error;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum IdentityErrorKind {
Empty,
InvalidStartChar(char),
InvalidChar { ch: char, position: usize },
}
#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub struct IdentityError {
pub kind: IdentityErrorKind,
pub raw: String,
}
impl fmt::Display for IdentityError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.kind {
IdentityErrorKind::Empty => {
write!(f, "identity must not be empty")
}
IdentityErrorKind::InvalidStartChar(ch) => {
write!(
f,
"identity {:?} must start with ASCII letter or underscore, found {:?}",
self.raw, ch
)
}
IdentityErrorKind::InvalidChar { ch, position } => {
write!(
f,
"identity {:?} contains invalid character {:?} at position {}",
self.raw, ch, position
)
}
}
}
}
fn validate_slug(raw: &str) -> Result<(), IdentityErrorKind> {
let mut chars = raw.chars().enumerate();
let (_, first) = chars.next().ok_or(IdentityErrorKind::Empty)?;
if !(first.is_ascii_alphabetic() || first == '_') {
return Err(IdentityErrorKind::InvalidStartChar(first));
}
for (pos, ch) in chars {
if !(ch.is_ascii_alphanumeric() || ch == '_' || ch == '-') {
return Err(IdentityErrorKind::InvalidChar { ch, position: pos });
}
}
Ok(())
}
macro_rules! define_identity {
($(#[$attr:meta])* $name:ident) => {
$(#[$attr])*
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct $name(String);
impl $name {
pub fn parse(value: impl Into<String>) -> Result<Self, IdentityError> {
let raw = value.into();
match validate_slug(&raw) {
Ok(()) => Ok(Self(raw)),
Err(kind) => Err(IdentityError { kind, raw }),
}
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl AsRef<str> for $name {
fn as_ref(&self) -> &str {
&self.0
}
}
impl fmt::Display for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl Serialize for $name {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&self.0)
}
}
impl<'de> Deserialize<'de> for $name {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let raw = String::deserialize(deserializer)?;
Self::parse(raw).map_err(serde::de::Error::custom)
}
}
};
}
define_identity!(
MachineId
);
define_identity!(
MachineInstanceId
);
define_identity!(
PhaseId
);
define_identity!(
InputVariantId
);
impl InputVariantId {
pub(crate) fn from_trusted_catalog_literal(value: &'static str) -> Self {
Self(value.to_owned())
}
}
define_identity!(
SignalVariantId
);
impl SignalVariantId {
pub(crate) fn from_trusted_catalog_literal(value: &'static str) -> Self {
Self(value.to_owned())
}
}
define_identity!(
EffectVariantId
);
impl EffectVariantId {
pub(crate) fn from_trusted_catalog_literal(value: &'static str) -> Self {
Self(value.to_owned())
}
}
define_identity!(
FieldId
);
define_identity!(
TransitionId
);
impl TransitionId {
pub(crate) fn from_trusted_catalog_literal(value: &'static str) -> Self {
Self(value.to_owned())
}
pub(crate) fn from_trusted_catalog_string(value: String) -> Self {
Self(value)
}
}
define_identity!(
RouteId
);
define_identity!(
ProtocolId
);
define_identity!(
ActorId
);
define_identity!(
NamedTypeId
);
define_identity!(
EnumTypeId
);
define_identity!(
EnumVariantId
);
define_identity!(
CompositionId
);
define_identity!(
CompositionDriverId
);
define_identity!(
TransactionPlanId
);
define_identity!(
TransactionTriggerId
);
define_identity!(
CompositionWitnessId
);
define_identity!(
EntryInputId
);
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct StorePrimitiveId(String);
impl StorePrimitiveId {
pub fn parse(value: impl Into<String>) -> Result<Self, IdentityError> {
let raw = value.into();
if raw.is_empty() {
return Err(IdentityError {
kind: IdentityErrorKind::Empty,
raw,
});
}
for (position, ch) in raw.chars().enumerate() {
if ch.is_control() || ch.is_whitespace() {
return Err(IdentityError {
kind: IdentityErrorKind::InvalidChar { ch, position },
raw,
});
}
}
Ok(Self(raw))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl AsRef<str> for StorePrimitiveId {
fn as_ref(&self) -> &str {
&self.0
}
}
impl fmt::Display for StorePrimitiveId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl Serialize for StorePrimitiveId {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&self.0)
}
}
impl<'de> Deserialize<'de> for StorePrimitiveId {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let raw = String::deserialize(deserializer)?;
Self::parse(raw).map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TypePathEnumPayloadAtom {
StringSet,
NamedSet(NamedTypeId),
String,
OptionalString,
Named(NamedTypeId),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct TypePathEnumPayloadField {
pub name: FieldId,
pub atom: TypePathEnumPayloadAtom,
}
impl TypePathEnumPayloadField {
pub fn string_set(name: &str) -> Self {
Self {
#[allow(clippy::expect_used)]
name: FieldId::parse(name).expect("valid structural enum field slug"),
atom: TypePathEnumPayloadAtom::StringSet,
}
}
pub fn named_set(name: &str, type_name: &str) -> Self {
#[allow(clippy::expect_used)]
let type_name = NamedTypeId::parse(type_name).expect("valid nested named-type slug");
Self {
#[allow(clippy::expect_used)]
name: FieldId::parse(name).expect("valid structural enum field slug"),
atom: TypePathEnumPayloadAtom::NamedSet(type_name),
}
}
pub fn string(name: &str) -> Self {
Self {
#[allow(clippy::expect_used)]
name: FieldId::parse(name).expect("valid structural enum field slug"),
atom: TypePathEnumPayloadAtom::String,
}
}
pub fn optional_string(name: &str) -> Self {
Self {
#[allow(clippy::expect_used)]
name: FieldId::parse(name).expect("valid structural enum field slug"),
atom: TypePathEnumPayloadAtom::OptionalString,
}
}
pub fn named(name: &str, type_name: &str) -> Self {
#[allow(clippy::expect_used)]
let type_name = NamedTypeId::parse(type_name).expect("valid nested named-type slug");
Self {
#[allow(clippy::expect_used)]
name: FieldId::parse(name).expect("valid structural enum field slug"),
atom: TypePathEnumPayloadAtom::Named(type_name),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct TypePathEnumStructuralVariant {
pub variant: EnumVariantId,
pub fields: Vec<TypePathEnumPayloadField>,
}
impl TypePathEnumStructuralVariant {
pub fn string_set(variant: &str, field: &str) -> Self {
Self {
#[allow(clippy::expect_used)]
variant: EnumVariantId::parse(variant).expect("valid enum variant slug"),
fields: vec![TypePathEnumPayloadField::string_set(field)],
}
}
pub fn named_set(variant: &str, field: &str, type_name: &str) -> Self {
Self {
#[allow(clippy::expect_used)]
variant: EnumVariantId::parse(variant).expect("valid enum variant slug"),
fields: vec![TypePathEnumPayloadField::named_set(field, type_name)],
}
}
pub fn with_fields(variant: &str, fields: Vec<TypePathEnumPayloadField>) -> Self {
Self {
#[allow(clippy::expect_used)]
variant: EnumVariantId::parse(variant).expect("valid enum variant slug"),
fields,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TypePathStructFieldAtom {
String,
Named(NamedTypeId),
OptionalNamed(NamedTypeId),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct TypePathStructField {
pub name: FieldId,
pub atom: TypePathStructFieldAtom,
}
impl TypePathStructField {
pub fn string(name: &str) -> Self {
Self {
#[allow(clippy::expect_used)]
name: FieldId::parse(name).expect("valid structural record field slug"),
atom: TypePathStructFieldAtom::String,
}
}
pub fn named(name: &str, type_name: &str) -> Self {
#[allow(clippy::expect_used)]
let type_name = NamedTypeId::parse(type_name).expect("valid nested named-type slug");
Self {
#[allow(clippy::expect_used)]
name: FieldId::parse(name).expect("valid structural record field slug"),
atom: TypePathStructFieldAtom::Named(type_name),
}
}
pub fn optional_named(name: &str, type_name: &str) -> Self {
#[allow(clippy::expect_used)]
let type_name = NamedTypeId::parse(type_name).expect("valid nested named-type slug");
Self {
#[allow(clippy::expect_used)]
name: FieldId::parse(name).expect("valid structural record field slug"),
atom: TypePathStructFieldAtom::OptionalNamed(type_name),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
pub enum RustTypeAtom {
U64,
U32,
U16,
U8,
Bool,
String,
StringEnum {
variants: Vec<EnumVariantId>,
},
TypePath(String),
TypePathFieldPresenceSet {
path: String,
fields: Vec<FieldId>,
},
TypePathStruct {
path: String,
fields: Vec<TypePathStructField>,
},
TypePathEnum {
path: String,
unit_variants: Vec<EnumVariantId>,
#[serde(default)]
structural_variants: Vec<TypePathEnumStructuralVariant>,
},
}
impl RustTypeAtom {
pub fn has_same_composition_domain_shape(&self, other: &Self) -> bool {
if self == other {
return true;
}
match (self, other) {
(Self::TypePath(_), Self::TypePath(_)) => true,
(
Self::TypePathFieldPresenceSet {
fields: left_fields,
..
},
Self::TypePathFieldPresenceSet {
fields: right_fields,
..
},
) => left_fields == right_fields,
(
Self::TypePathStruct {
fields: left_fields,
..
},
Self::TypePathStruct {
fields: right_fields,
..
},
) => left_fields == right_fields,
(
Self::TypePathEnum {
unit_variants: left_units,
structural_variants: left_structural,
..
},
Self::TypePathEnum {
unit_variants: right_units,
structural_variants: right_structural,
..
},
) => left_units == right_units && left_structural == right_structural,
_ => false,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct NamedTypeBinding {
pub name: NamedTypeId,
pub rust: RustTypeAtom,
}
impl NamedTypeBinding {
pub fn u64(name: &str) -> Self {
Self {
#[allow(clippy::expect_used)]
name: NamedTypeId::parse(name).expect("valid named-type slug"),
rust: RustTypeAtom::U64,
}
}
pub fn string(name: &str) -> Self {
Self {
#[allow(clippy::expect_used)]
name: NamedTypeId::parse(name).expect("valid named-type slug"),
rust: RustTypeAtom::String,
}
}
pub fn string_enum(name: &str, variants: &[&str]) -> Self {
assert!(
!variants.is_empty(),
"string enum named-type bindings require at least one variant"
);
Self {
#[allow(clippy::expect_used)]
name: NamedTypeId::parse(name).expect("valid named-type slug"),
rust: RustTypeAtom::StringEnum {
variants: variants
.iter()
.map(|variant| {
#[allow(clippy::expect_used)]
EnumVariantId::parse(*variant).expect("valid enum variant slug")
})
.collect(),
},
}
}
pub fn type_path(name: &str, rust_path: impl Into<String>) -> Self {
Self {
#[allow(clippy::expect_used)]
name: NamedTypeId::parse(name).expect("valid named-type slug"),
rust: RustTypeAtom::TypePath(rust_path.into()),
}
}
pub fn type_path_field_presence_set(
name: &str,
rust_path: impl Into<String>,
fields: &[&str],
) -> Self {
assert!(
!fields.is_empty(),
"field-presence named-type bindings require at least one field"
);
Self {
#[allow(clippy::expect_used)]
name: NamedTypeId::parse(name).expect("valid named-type slug"),
rust: RustTypeAtom::TypePathFieldPresenceSet {
path: rust_path.into(),
fields: fields
.iter()
.map(|field| {
#[allow(clippy::expect_used)]
FieldId::parse(*field).expect("valid field-presence slug")
})
.collect(),
},
}
}
pub fn type_path_struct(
name: &str,
rust_path: impl Into<String>,
fields: Vec<TypePathStructField>,
) -> Self {
assert!(
!fields.is_empty(),
"struct named-type bindings require at least one field"
);
Self {
#[allow(clippy::expect_used)]
name: NamedTypeId::parse(name).expect("valid named-type slug"),
rust: RustTypeAtom::TypePathStruct {
path: rust_path.into(),
fields,
},
}
}
pub fn type_path_enum(
name: &str,
rust_path: impl Into<String>,
unit_variants: &[&str],
) -> Self {
assert!(
!unit_variants.is_empty(),
"type-path enum named-type bindings require at least one unit variant"
);
Self {
#[allow(clippy::expect_used)]
name: NamedTypeId::parse(name).expect("valid named-type slug"),
rust: RustTypeAtom::TypePathEnum {
path: rust_path.into(),
unit_variants: unit_variants
.iter()
.map(|variant| {
#[allow(clippy::expect_used)]
EnumVariantId::parse(*variant).expect("valid enum variant slug")
})
.collect(),
structural_variants: Vec::new(),
},
}
}
pub fn type_path_enum_with_structural_variants(
name: &str,
rust_path: impl Into<String>,
unit_variants: &[&str],
structural_variants: Vec<TypePathEnumStructuralVariant>,
) -> Self {
let mut binding = Self::type_path_enum(name, rust_path, unit_variants);
if let RustTypeAtom::TypePathEnum {
structural_variants: variants,
..
} = &mut binding.rust
{
*variants = structural_variants;
}
binding
}
}