1use serde::{Deserialize, Deserializer, Serialize, Serializer};
20use std::fmt;
21use thiserror::Error;
22
23#[derive(Debug, Clone, PartialEq, Eq)]
25pub enum IdentityErrorKind {
26 Empty,
28 InvalidStartChar(char),
30 InvalidChar { ch: char, position: usize },
32}
33
34#[derive(Debug, Clone, PartialEq, Eq, Error)]
36pub struct IdentityError {
37 pub kind: IdentityErrorKind,
38 pub raw: String,
39}
40
41impl fmt::Display for IdentityError {
42 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43 match &self.kind {
44 IdentityErrorKind::Empty => {
45 write!(f, "identity must not be empty")
46 }
47 IdentityErrorKind::InvalidStartChar(ch) => {
48 write!(
49 f,
50 "identity {:?} must start with ASCII letter or underscore, found {:?}",
51 self.raw, ch
52 )
53 }
54 IdentityErrorKind::InvalidChar { ch, position } => {
55 write!(
56 f,
57 "identity {:?} contains invalid character {:?} at position {}",
58 self.raw, ch, position
59 )
60 }
61 }
62 }
63}
64
65fn validate_slug(raw: &str) -> Result<(), IdentityErrorKind> {
66 let mut chars = raw.chars().enumerate();
67 let (_, first) = chars.next().ok_or(IdentityErrorKind::Empty)?;
68 if !(first.is_ascii_alphabetic() || first == '_') {
69 return Err(IdentityErrorKind::InvalidStartChar(first));
70 }
71 for (pos, ch) in chars {
72 if !(ch.is_ascii_alphanumeric() || ch == '_' || ch == '-') {
73 return Err(IdentityErrorKind::InvalidChar { ch, position: pos });
74 }
75 }
76 Ok(())
77}
78
79macro_rules! define_identity {
80 ($(#[$attr:meta])* $name:ident) => {
81 $(#[$attr])*
82 #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
83 pub struct $name(String);
84
85 impl $name {
86 pub fn parse(value: impl Into<String>) -> Result<Self, IdentityError> {
88 let raw = value.into();
89 match validate_slug(&raw) {
90 Ok(()) => Ok(Self(raw)),
91 Err(kind) => Err(IdentityError { kind, raw }),
92 }
93 }
94
95 pub fn as_str(&self) -> &str {
97 &self.0
98 }
99 }
100
101 impl AsRef<str> for $name {
102 fn as_ref(&self) -> &str {
103 &self.0
104 }
105 }
106
107 impl fmt::Display for $name {
108 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109 f.write_str(&self.0)
110 }
111 }
112
113 impl Serialize for $name {
114 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
115 serializer.serialize_str(&self.0)
116 }
117 }
118
119 impl<'de> Deserialize<'de> for $name {
120 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
121 let raw = String::deserialize(deserializer)?;
122 Self::parse(raw).map_err(serde::de::Error::custom)
123 }
124 }
125 };
126}
127
128define_identity!(
129 MachineId
131);
132define_identity!(
133 MachineInstanceId
135);
136define_identity!(
137 PhaseId
139);
140define_identity!(
141 InputVariantId
143);
144
145impl InputVariantId {
146 pub(crate) fn from_trusted_catalog_literal(value: &'static str) -> Self {
152 Self(value.to_owned())
153 }
154}
155define_identity!(
156 SignalVariantId
158);
159
160impl SignalVariantId {
161 pub(crate) fn from_trusted_catalog_literal(value: &'static str) -> Self {
163 Self(value.to_owned())
164 }
165}
166define_identity!(
167 EffectVariantId
169);
170
171impl EffectVariantId {
172 pub(crate) fn from_trusted_catalog_literal(value: &'static str) -> Self {
174 Self(value.to_owned())
175 }
176}
177define_identity!(
178 FieldId
180);
181define_identity!(
182 TransitionId
184);
185
186impl TransitionId {
187 pub(crate) fn from_trusted_catalog_literal(value: &'static str) -> Self {
189 Self(value.to_owned())
190 }
191
192 pub(crate) fn from_trusted_catalog_string(value: String) -> Self {
195 Self(value)
196 }
197}
198define_identity!(
199 RouteId
201);
202define_identity!(
203 ProtocolId
205);
206define_identity!(
207 ActorId
209);
210define_identity!(
211 NamedTypeId
213);
214define_identity!(
215 EnumTypeId
217);
218define_identity!(
219 EnumVariantId
221);
222define_identity!(
223 CompositionId
225);
226define_identity!(
227 CompositionDriverId
229);
230define_identity!(
231 TransactionPlanId
233);
234define_identity!(
235 TransactionTriggerId
237);
238define_identity!(
239 CompositionWitnessId
241);
242define_identity!(
243 EntryInputId
245);
246
247#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
254pub struct StorePrimitiveId(String);
255
256impl StorePrimitiveId {
257 pub fn parse(value: impl Into<String>) -> Result<Self, IdentityError> {
258 let raw = value.into();
259 if raw.is_empty() {
260 return Err(IdentityError {
261 kind: IdentityErrorKind::Empty,
262 raw,
263 });
264 }
265 for (position, ch) in raw.chars().enumerate() {
266 if ch.is_control() || ch.is_whitespace() {
267 return Err(IdentityError {
268 kind: IdentityErrorKind::InvalidChar { ch, position },
269 raw,
270 });
271 }
272 }
273 Ok(Self(raw))
274 }
275
276 pub fn as_str(&self) -> &str {
277 &self.0
278 }
279}
280
281impl AsRef<str> for StorePrimitiveId {
282 fn as_ref(&self) -> &str {
283 &self.0
284 }
285}
286
287impl fmt::Display for StorePrimitiveId {
288 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
289 f.write_str(&self.0)
290 }
291}
292
293impl Serialize for StorePrimitiveId {
294 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
295 serializer.serialize_str(&self.0)
296 }
297}
298
299impl<'de> Deserialize<'de> for StorePrimitiveId {
300 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
301 let raw = String::deserialize(deserializer)?;
302 Self::parse(raw).map_err(serde::de::Error::custom)
303 }
304}
305
306#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
308#[serde(rename_all = "snake_case")]
309pub enum TypePathEnumPayloadAtom {
310 StringSet,
312 NamedSet(NamedTypeId),
314 String,
316 OptionalString,
318 Named(NamedTypeId),
320}
321
322#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
324pub struct TypePathEnumPayloadField {
325 pub name: FieldId,
326 pub atom: TypePathEnumPayloadAtom,
327}
328
329impl TypePathEnumPayloadField {
330 pub fn string_set(name: &str) -> Self {
332 Self {
333 #[allow(clippy::expect_used)]
334 name: FieldId::parse(name).expect("valid structural enum field slug"),
335 atom: TypePathEnumPayloadAtom::StringSet,
336 }
337 }
338
339 pub fn named_set(name: &str, type_name: &str) -> Self {
342 #[allow(clippy::expect_used)]
343 let type_name = NamedTypeId::parse(type_name).expect("valid nested named-type slug");
344 Self {
345 #[allow(clippy::expect_used)]
346 name: FieldId::parse(name).expect("valid structural enum field slug"),
347 atom: TypePathEnumPayloadAtom::NamedSet(type_name),
348 }
349 }
350
351 pub fn string(name: &str) -> Self {
353 Self {
354 #[allow(clippy::expect_used)]
355 name: FieldId::parse(name).expect("valid structural enum field slug"),
356 atom: TypePathEnumPayloadAtom::String,
357 }
358 }
359
360 pub fn optional_string(name: &str) -> Self {
362 Self {
363 #[allow(clippy::expect_used)]
364 name: FieldId::parse(name).expect("valid structural enum field slug"),
365 atom: TypePathEnumPayloadAtom::OptionalString,
366 }
367 }
368
369 pub fn named(name: &str, type_name: &str) -> Self {
372 #[allow(clippy::expect_used)]
373 let type_name = NamedTypeId::parse(type_name).expect("valid nested named-type slug");
374 Self {
375 #[allow(clippy::expect_used)]
376 name: FieldId::parse(name).expect("valid structural enum field slug"),
377 atom: TypePathEnumPayloadAtom::Named(type_name),
378 }
379 }
380}
381
382#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
384pub struct TypePathEnumStructuralVariant {
385 pub variant: EnumVariantId,
386 pub fields: Vec<TypePathEnumPayloadField>,
387}
388
389impl TypePathEnumStructuralVariant {
390 pub fn string_set(variant: &str, field: &str) -> Self {
392 Self {
393 #[allow(clippy::expect_used)]
394 variant: EnumVariantId::parse(variant).expect("valid enum variant slug"),
395 fields: vec![TypePathEnumPayloadField::string_set(field)],
396 }
397 }
398
399 pub fn named_set(variant: &str, field: &str, type_name: &str) -> Self {
402 Self {
403 #[allow(clippy::expect_used)]
404 variant: EnumVariantId::parse(variant).expect("valid enum variant slug"),
405 fields: vec![TypePathEnumPayloadField::named_set(field, type_name)],
406 }
407 }
408
409 pub fn with_fields(variant: &str, fields: Vec<TypePathEnumPayloadField>) -> Self {
411 Self {
412 #[allow(clippy::expect_used)]
413 variant: EnumVariantId::parse(variant).expect("valid enum variant slug"),
414 fields,
415 }
416 }
417}
418
419#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
421#[serde(rename_all = "snake_case")]
422pub enum TypePathStructFieldAtom {
423 String,
424 Named(NamedTypeId),
425 OptionalNamed(NamedTypeId),
426}
427
428#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
430pub struct TypePathStructField {
431 pub name: FieldId,
432 pub atom: TypePathStructFieldAtom,
433}
434
435impl TypePathStructField {
436 pub fn string(name: &str) -> Self {
438 Self {
439 #[allow(clippy::expect_used)]
440 name: FieldId::parse(name).expect("valid structural record field slug"),
441 atom: TypePathStructFieldAtom::String,
442 }
443 }
444
445 pub fn named(name: &str, type_name: &str) -> Self {
447 #[allow(clippy::expect_used)]
448 let type_name = NamedTypeId::parse(type_name).expect("valid nested named-type slug");
449 Self {
450 #[allow(clippy::expect_used)]
451 name: FieldId::parse(name).expect("valid structural record field slug"),
452 atom: TypePathStructFieldAtom::Named(type_name),
453 }
454 }
455
456 pub fn optional_named(name: &str, type_name: &str) -> Self {
458 #[allow(clippy::expect_used)]
459 let type_name = NamedTypeId::parse(type_name).expect("valid nested named-type slug");
460 Self {
461 #[allow(clippy::expect_used)]
462 name: FieldId::parse(name).expect("valid structural record field slug"),
463 atom: TypePathStructFieldAtom::OptionalNamed(type_name),
464 }
465 }
466}
467
468#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
475#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
476pub enum RustTypeAtom {
477 U64,
478 U32,
479 U16,
480 U8,
481 Bool,
482 String,
483 StringEnum {
489 variants: Vec<EnumVariantId>,
490 },
491 TypePath(String),
493 TypePathFieldPresenceSet {
496 path: String,
497 fields: Vec<FieldId>,
498 },
499 TypePathStruct {
502 path: String,
503 fields: Vec<TypePathStructField>,
504 },
505 TypePathEnum {
508 path: String,
509 unit_variants: Vec<EnumVariantId>,
510 #[serde(default)]
511 structural_variants: Vec<TypePathEnumStructuralVariant>,
512 },
513}
514
515impl RustTypeAtom {
516 pub fn has_same_composition_domain_shape(&self, other: &Self) -> bool {
525 if self == other {
526 return true;
527 }
528
529 match (self, other) {
530 (Self::TypePath(_), Self::TypePath(_)) => true,
531 (
532 Self::TypePathFieldPresenceSet {
533 fields: left_fields,
534 ..
535 },
536 Self::TypePathFieldPresenceSet {
537 fields: right_fields,
538 ..
539 },
540 ) => left_fields == right_fields,
541 (
542 Self::TypePathStruct {
543 fields: left_fields,
544 ..
545 },
546 Self::TypePathStruct {
547 fields: right_fields,
548 ..
549 },
550 ) => left_fields == right_fields,
551 (
552 Self::TypePathEnum {
553 unit_variants: left_units,
554 structural_variants: left_structural,
555 ..
556 },
557 Self::TypePathEnum {
558 unit_variants: right_units,
559 structural_variants: right_structural,
560 ..
561 },
562 ) => left_units == right_units && left_structural == right_structural,
563 _ => false,
564 }
565 }
566}
567
568#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
574pub struct NamedTypeBinding {
575 pub name: NamedTypeId,
576 pub rust: RustTypeAtom,
577}
578
579impl NamedTypeBinding {
580 pub fn u64(name: &str) -> Self {
587 Self {
588 #[allow(clippy::expect_used)]
589 name: NamedTypeId::parse(name).expect("valid named-type slug"),
590 rust: RustTypeAtom::U64,
591 }
592 }
593
594 pub fn string(name: &str) -> Self {
596 Self {
597 #[allow(clippy::expect_used)]
598 name: NamedTypeId::parse(name).expect("valid named-type slug"),
599 rust: RustTypeAtom::String,
600 }
601 }
602
603 pub fn string_enum(name: &str, variants: &[&str]) -> Self {
609 assert!(
610 !variants.is_empty(),
611 "string enum named-type bindings require at least one variant"
612 );
613 Self {
614 #[allow(clippy::expect_used)]
615 name: NamedTypeId::parse(name).expect("valid named-type slug"),
616 rust: RustTypeAtom::StringEnum {
617 variants: variants
618 .iter()
619 .map(|variant| {
620 #[allow(clippy::expect_used)]
621 EnumVariantId::parse(*variant).expect("valid enum variant slug")
622 })
623 .collect(),
624 },
625 }
626 }
627
628 pub fn type_path(name: &str, rust_path: impl Into<String>) -> Self {
631 Self {
632 #[allow(clippy::expect_used)]
633 name: NamedTypeId::parse(name).expect("valid named-type slug"),
634 rust: RustTypeAtom::TypePath(rust_path.into()),
635 }
636 }
637
638 pub fn type_path_field_presence_set(
641 name: &str,
642 rust_path: impl Into<String>,
643 fields: &[&str],
644 ) -> Self {
645 assert!(
646 !fields.is_empty(),
647 "field-presence named-type bindings require at least one field"
648 );
649 Self {
650 #[allow(clippy::expect_used)]
651 name: NamedTypeId::parse(name).expect("valid named-type slug"),
652 rust: RustTypeAtom::TypePathFieldPresenceSet {
653 path: rust_path.into(),
654 fields: fields
655 .iter()
656 .map(|field| {
657 #[allow(clippy::expect_used)]
658 FieldId::parse(*field).expect("valid field-presence slug")
659 })
660 .collect(),
661 },
662 }
663 }
664
665 pub fn type_path_struct(
669 name: &str,
670 rust_path: impl Into<String>,
671 fields: Vec<TypePathStructField>,
672 ) -> Self {
673 assert!(
674 !fields.is_empty(),
675 "struct named-type bindings require at least one field"
676 );
677 Self {
678 #[allow(clippy::expect_used)]
679 name: NamedTypeId::parse(name).expect("valid named-type slug"),
680 rust: RustTypeAtom::TypePathStruct {
681 path: rust_path.into(),
682 fields,
683 },
684 }
685 }
686
687 pub fn type_path_enum(
690 name: &str,
691 rust_path: impl Into<String>,
692 unit_variants: &[&str],
693 ) -> Self {
694 assert!(
695 !unit_variants.is_empty(),
696 "type-path enum named-type bindings require at least one unit variant"
697 );
698 Self {
699 #[allow(clippy::expect_used)]
700 name: NamedTypeId::parse(name).expect("valid named-type slug"),
701 rust: RustTypeAtom::TypePathEnum {
702 path: rust_path.into(),
703 unit_variants: unit_variants
704 .iter()
705 .map(|variant| {
706 #[allow(clippy::expect_used)]
707 EnumVariantId::parse(*variant).expect("valid enum variant slug")
708 })
709 .collect(),
710 structural_variants: Vec::new(),
711 },
712 }
713 }
714
715 pub fn type_path_enum_with_structural_variants(
718 name: &str,
719 rust_path: impl Into<String>,
720 unit_variants: &[&str],
721 structural_variants: Vec<TypePathEnumStructuralVariant>,
722 ) -> Self {
723 let mut binding = Self::type_path_enum(name, rust_path, unit_variants);
724 if let RustTypeAtom::TypePathEnum {
725 structural_variants: variants,
726 ..
727 } = &mut binding.rust
728 {
729 *variants = structural_variants;
730 }
731 binding
732 }
733}