Skip to main content

wasm4pm_compat/
ids.rs

1//! Zero-cost, kind-typed identifier wrappers.
2//!
3//! Process evidence is full of integer identifiers — event ids, object ids,
4//! activity ids, relation ids, trace ids. Passed around as bare `u64`/`u32`
5//! they are trivially interchangeable, which is exactly how a dangling link or
6//! a flattening bug slips in: nothing stops you handing an *object* id where an
7//! *event* id is required.
8//!
9//! Each id here is a `#[repr(transparent)]` newtype carrying a `PhantomData<K>`
10//! **kind marker**, so [`crate::ids::EventId`] and [`crate::ids::ObjectId`] are *distinct types*
11//! even though both wrap a `u64`. Mixing them is a **compile error**, not a
12//! debugging session. The `K` parameter additionally lets a caller stamp ids
13//! with a *namespace* (e.g. a witness or a log identity) so ids from different
14//! origins cannot be confused either.
15//!
16//! These wrappers are **structure only**: they identify, they do not resolve.
17//! Resolving an id to the value it names (and validating that the link exists)
18//! is an engine concern — graduate to `wasm4pm` for that.
19//!
20//! ## String-backed name types
21//!
22//! [`crate::ids::ObjectTypeName`] and [`crate::ids::EventTypeName`] carry the actual string label
23//! (e.g. `"order"`, `"place_order"`) rather than an interned integer handle.
24//! They are structurally distinct from the integer-backed types above — mixing
25//! them is a compile error. Use the integer-backed [`crate::ids::ObjectTypeId`] /
26//! [`crate::ids::EventTypeId`] when you hold an interned handle; use the string-backed
27//! types when you hold the human-readable label itself.
28//!
29//! ## `id_of` — phantom-typed marker constructor
30//!
31//! [`crate::ids::id_of`] is a zero-cost free function that constructs any [`crate::ids::TypedId`]
32//! implementor from a raw value while anchoring it to an explicit kind marker.
33//! It is the *canonical* way to build a typed id when the kind is known at the
34//! call site: `id_of::<EventId<MyLog>>(7)` rather than `EventId::<MyLog>::new(7)`.
35
36use core::marker::PhantomData;
37use std::borrow::Cow;
38
39/// Sealed marker trait shared by every kind-typed identifier in this module.
40///
41/// # What this is
42///
43/// `TypedId` is a *sealed* trait: it is implemented only by the newtypes
44/// declared in this module ([`crate::ids::EventId`], [`crate::ids::ObjectId`], [`crate::ids::CaseId`], etc.) and
45/// cannot be implemented outside the crate. It lets generic code express
46/// "any typed id" without reaching for a raw `u64`/`u32`.
47///
48/// # What this is NOT
49///
50/// This is not a resolving interface. A `TypedId` still only **names** an
51/// entity — it does not look it up, validate liveness, or dereference a link.
52/// Graduate to `wasm4pm` for resolution.
53///
54/// # When to graduate
55///
56/// When you need to dereference an id to the value it names, or validate that
57/// a link exists, move that logic to the `wasm4pm` execution engine.
58///
59/// # Example
60///
61/// ```
62/// use wasm4pm_compat::ids::{EventId, TypedId};
63/// enum MyLog {}
64/// fn id_is_positive<I: TypedId>(id: &I) -> bool { !id.is_zero() }
65/// let ev = EventId::<MyLog>::new(7);
66/// assert!(id_is_positive(&ev));
67/// ```
68pub trait TypedId: sealed::SealedId + Copy + Eq + core::hash::Hash + core::fmt::Debug {
69    /// The underlying raw primitive type (`u64` or `u32`).
70    type Raw: Copy + Eq + core::hash::Hash + core::fmt::Debug;
71
72    /// Returns the underlying raw value.
73    fn raw_value(&self) -> Self::Raw;
74
75    /// Returns `true` when the raw value is the zero sentinel.
76    ///
77    /// Zero is conventionally "no id" in many process-mining tools; this method
78    /// lets generic code check for the sentinel without knowing the raw type.
79    fn is_zero(&self) -> bool;
80}
81
82mod sealed {
83    pub trait SealedId {}
84}
85
86/// Implements [`TypedId`] and [`sealed::SealedId`] for a given newtype.
87macro_rules! impl_typed_id {
88    ($name:ident, $raw:ty, $zero:expr) => {
89        impl<K> sealed::SealedId for $name<K> {}
90        impl<K> TypedId for $name<K> {
91            type Raw = $raw;
92            #[inline]
93            fn raw_value(&self) -> $raw {
94                self.raw
95            }
96            #[inline]
97            fn is_zero(&self) -> bool {
98                self.raw == $zero
99            }
100        }
101    };
102}
103
104/// Declares a `#[repr(transparent)]` kind-typed id newtype over `$raw`.
105macro_rules! typed_id {
106    ($(#[$meta:meta])* $name:ident, $raw:ty) => {
107        $(#[$meta])*
108        ///
109        /// Zero-cost `#[repr(transparent)]` wrapper carrying a `PhantomData<K>`
110        /// kind marker. Structure-only: it names an entity, it does not resolve
111        /// or validate the link. Graduate to `wasm4pm` to dereference it.
112        #[repr(transparent)]
113        pub struct $name<K> {
114            raw: $raw,
115            _kind: PhantomData<K>,
116        }
117
118        impl<K> $name<K> {
119            #[doc = concat!("Wraps a raw `", stringify!($raw), "` as a typed [`", stringify!($name), "`].")]
120            ///
121            /// # Examples
122            ///
123            /// ```
124            #[doc = concat!("use wasm4pm_compat::ids::", stringify!($name), ";")]
125            /// enum Local {}
126            #[doc = concat!("let id = ", stringify!($name), "::<Local>::new(7);")]
127            /// assert_eq!(id.raw(), 7);
128            /// ```
129            #[inline]
130            pub const fn new(raw: $raw) -> Self {
131                Self { raw, _kind: PhantomData }
132            }
133
134            #[doc = concat!("Returns the underlying raw `", stringify!($raw), "`.")]
135            ///
136            /// # Examples
137            ///
138            /// ```
139            #[doc = concat!("use wasm4pm_compat::ids::", stringify!($name), ";")]
140            /// enum Local {}
141            #[doc = concat!("assert_eq!(", stringify!($name), "::<Local>::new(42).raw(), 42);")]
142            /// ```
143            #[inline]
144            pub const fn raw(self) -> $raw {
145                self.raw
146            }
147
148            #[doc = concat!("Consumes `self` and returns the underlying raw `", stringify!($raw), "` value.")]
149            ///
150            /// Identical to [`raw`](Self::raw); provided for newtype-wrapper
151            /// ergonomics so callers can use the same `into_inner()` idiom
152            /// across all ID kinds.
153            ///
154            /// # Examples
155            ///
156            /// ```
157            #[doc = concat!("use wasm4pm_compat::ids::", stringify!($name), ";")]
158            /// enum Local {}
159            #[doc = concat!("assert_eq!(", stringify!($name), "::<Local>::new(5).into_inner(), 5);")]
160            /// ```
161            #[inline]
162            pub const fn into_inner(self) -> $raw {
163                self.raw
164            }
165
166            #[doc = concat!("Borrows the underlying raw `", stringify!($raw), "` value.")]
167            ///
168            /// Identical to [`AsRef`] but using the newtype-wrapper ergonomic
169            /// name `as_inner()` so callers can use a consistent idiom across
170            /// all ID kinds.
171            ///
172            /// # Examples
173            ///
174            /// ```
175            #[doc = concat!("use wasm4pm_compat::ids::", stringify!($name), ";")]
176            /// enum Local {}
177            #[doc = concat!("assert_eq!(*", stringify!($name), "::<Local>::new(9).as_inner(), 9);")]
178            /// ```
179            #[inline]
180            pub const fn as_inner(&self) -> &$raw {
181                &self.raw
182            }
183        }
184
185        // Manual derives so `K` need not itself be `Clone`/`Copy`/etc.
186        impl<K> Clone for $name<K> {
187            #[inline]
188            fn clone(&self) -> Self { *self }
189        }
190        impl<K> Copy for $name<K> {}
191        impl<K> PartialEq for $name<K> {
192            #[inline]
193            fn eq(&self, other: &Self) -> bool { self.raw == other.raw }
194        }
195        impl<K> Eq for $name<K> {}
196        impl<K> core::hash::Hash for $name<K> {
197            #[inline]
198            fn hash<H: core::hash::Hasher>(&self, state: &mut H) { self.raw.hash(state); }
199        }
200        impl<K> core::fmt::Debug for $name<K> {
201            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
202                f.debug_tuple(stringify!($name)).field(&self.raw).finish()
203            }
204        }
205        /// Displays the raw numeric value prefixed by the type name, e.g. `EventId(7)`.
206        impl<K> core::fmt::Display for $name<K> {
207            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
208                write!(f, "{}({})", stringify!($name), self.raw)
209            }
210        }
211        impl<K> PartialOrd for $name<K> {
212            #[inline]
213            fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
214                Some(self.cmp(other))
215            }
216        }
217        impl<K> Ord for $name<K> {
218            #[inline]
219            fn cmp(&self, other: &Self) -> core::cmp::Ordering {
220                self.raw.cmp(&other.raw)
221            }
222        }
223    };
224}
225
226typed_id!(
227    /// Identifies a single event within a log (the atom of process evidence).
228    #[doc(alias = "event identifier")]
229    #[doc(alias = "event id")]
230    EventId, u64
231);
232typed_id!(
233    /// Identifies a single object in an object-centric log (OCEL).
234    ObjectId, u64
235);
236typed_id!(
237    /// Identifies an activity (the name an event realizes), interned to `u32`.
238    ActivityId, u32
239);
240typed_id!(
241    /// Identifies an event-to-object relation (a qualified link in OCEL).
242    RelationId, u32
243);
244typed_id!(
245    /// Identifies a trace (case) — a sequence of events for one process instance.
246    TraceId, u64
247);
248typed_id!(
249    /// Identifies an object-type class in an object-centric log (OCEL).
250    ///
251    /// In OCEL every object belongs to exactly one object type (e.g. `"order"`,
252    /// `"item"`, `"payment"`). [`ObjectTypeId`] is an interned `u32` handle for
253    /// that type name. It is structurally distinct from [`ObjectId`] (which
254    /// identifies a *specific* object instance) and from [`EventTypeId`] (which
255    /// identifies an activity type). Confusing them is a compile error.
256    ObjectTypeId, u32
257);
258typed_id!(
259    /// Identifies an event-type (activity label) in a typed event log.
260    ///
261    /// [`EventTypeId`] is an interned `u32` handle for an activity name at the
262    /// *type* level (e.g. `"place_order"` as a class). It is structurally
263    /// distinct from [`ActivityId`] (which may carry log-local interning) and
264    /// from [`EventId`] (which identifies a *specific* event occurrence).
265    /// Confusing them is a compile error.
266    EventTypeId, u32
267);
268typed_id!(
269    /// Identifies a case in a case-centric (XES-style) log.
270    ///
271    /// [`CaseId`] and [`TraceId`] are intentionally distinct: [`CaseId`] names
272    /// the case *attribute* as parsed from an external format (e.g. XES
273    /// `concept:name`), while [`TraceId`] names a structural trace position
274    /// within an already-admitted [`crate::event_log::EventLog`]. Mixing them is
275    /// a compile error, not a naming convention.
276    CaseId, u64
277);
278
279// ── TypedId sealed-trait implementations ─────────────────────────────────────
280
281impl_typed_id!(EventId, u64, 0u64);
282impl_typed_id!(ObjectId, u64, 0u64);
283impl_typed_id!(ActivityId, u32, 0u32);
284impl_typed_id!(RelationId, u32, 0u32);
285impl_typed_id!(TraceId, u64, 0u64);
286impl_typed_id!(ObjectTypeId, u32, 0u32);
287impl_typed_id!(EventTypeId, u32, 0u32);
288impl_typed_id!(CaseId, u64, 0u64);
289
290// ── String-backed name types ──────────────────────────────────────────────────
291
292/// Carries the human-readable label of an object-type class in an OCEL log.
293///
294/// # What this is
295///
296/// `ObjectTypeName<K>` holds the actual string label (e.g. `"order"`, `"item"`,
297/// `"payment"`) of an OCEL object-type class. It is the string-backed counterpart
298/// to [`ObjectTypeId`] (which holds an interned `u32` handle). Use this type
299/// when the label itself — not an integer handle — is the identity token.
300///
301/// The inner value is `Cow<'static, str>` so that both compile-time string
302/// literals (`&'static str`) and heap-allocated `String`s (as `Owned`) can be
303/// held without copying when the source is already static.
304///
305/// # What this is NOT
306///
307/// This is not a resolution mechanism. Knowing the label does not prove the
308/// type exists in a log or that any object belongs to it. Graduate to
309/// `wasm4pm` for type-membership validation.
310///
311/// # When to graduate
312///
313/// When you need to resolve an `ObjectTypeName` to its object members, or
314/// validate type membership, graduate to the `wasm4pm` execution engine.
315///
316/// # Examples
317///
318/// ```ignore
319/// use wasm4pm_compat::ids::ObjectTypeName;
320/// enum MyLog {}
321/// let order_type = ObjectTypeName::<MyLog>::from_static("order");
322/// assert_eq!(order_type.as_str(), "order");
323/// ```
324pub struct ObjectTypeName<K> {
325    label: Cow<'static, str>,
326    _kind: PhantomData<K>,
327}
328
329impl<K> ObjectTypeName<K> {
330    /// Wraps a `&'static str` label without allocation.
331    ///
332    /// # Examples
333    ///
334    /// ```ignore
335    /// use wasm4pm_compat::ids::ObjectTypeName;
336    /// enum MyLog {}
337    /// let t = ObjectTypeName::<MyLog>::from_static("order");
338    /// assert_eq!(t.as_str(), "order");
339    /// ```
340    #[inline]
341    pub fn from_static(label: &'static str) -> Self {
342        Self {
343            label: Cow::Borrowed(label),
344            _kind: PhantomData,
345        }
346    }
347
348    /// Wraps an owned `String` label, allocating on the heap.
349    ///
350    /// # Examples
351    ///
352    /// ```ignore
353    /// use wasm4pm_compat::ids::ObjectTypeName;
354    /// enum MyLog {}
355    /// let t = ObjectTypeName::<MyLog>::from_owned(String::from("item"));
356    /// assert_eq!(t.as_str(), "item");
357    /// ```
358    #[inline]
359    pub fn from_owned(label: String) -> Self {
360        Self {
361            label: Cow::Owned(label),
362            _kind: PhantomData,
363        }
364    }
365
366    /// Returns the string label.
367    ///
368    /// # Examples
369    ///
370    /// ```ignore
371    /// use wasm4pm_compat::ids::ObjectTypeName;
372    /// enum MyLog {}
373    /// assert_eq!(ObjectTypeName::<MyLog>::from_static("payment").as_str(), "payment");
374    /// ```
375    #[inline]
376    pub fn as_str(&self) -> &str {
377        &self.label
378    }
379}
380
381impl<K> Clone for ObjectTypeName<K> {
382    #[inline]
383    fn clone(&self) -> Self {
384        Self {
385            label: self.label.clone(),
386            _kind: PhantomData,
387        }
388    }
389}
390impl<K> PartialEq for ObjectTypeName<K> {
391    #[inline]
392    fn eq(&self, other: &Self) -> bool {
393        self.label == other.label
394    }
395}
396impl<K> Eq for ObjectTypeName<K> {}
397impl<K> core::hash::Hash for ObjectTypeName<K> {
398    #[inline]
399    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
400        self.label.hash(state);
401    }
402}
403impl<K> core::fmt::Debug for ObjectTypeName<K> {
404    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
405        f.debug_tuple("ObjectTypeName").field(&self.label).finish()
406    }
407}
408/// Displays the string label prefixed by the type name, e.g. `ObjectTypeName("order")`.
409impl<K> core::fmt::Display for ObjectTypeName<K> {
410    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
411        write!(f, "ObjectTypeName(\"{}\")", self.label)
412    }
413}
414impl<K> PartialOrd for ObjectTypeName<K> {
415    #[inline]
416    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
417        Some(self.cmp(other))
418    }
419}
420impl<K> Ord for ObjectTypeName<K> {
421    #[inline]
422    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
423        self.label.cmp(&other.label)
424    }
425}
426
427/// Carries the human-readable label of an event-type (activity) in a typed log.
428///
429/// # What this is
430///
431/// `EventTypeName<K>` holds the actual string label (e.g. `"place_order"`,
432/// `"ship_item"`) of an activity class. It is the string-backed counterpart to
433/// [`EventTypeId`] (which holds an interned `u32` handle). Use this type when
434/// the label itself — not an integer handle — is the identity token.
435///
436/// The inner value is `Cow<'static, str>` so that both compile-time string
437/// literals and heap-allocated `String`s can be held without copying when the
438/// source is already static.
439///
440/// # What this is NOT
441///
442/// This is not a resolution mechanism. Knowing the label does not prove the
443/// activity type exists in a specific log. Graduate to `wasm4pm` for that.
444///
445/// # When to graduate
446///
447/// When you need to resolve an `EventTypeName` to event occurrences, or
448/// validate activity membership, graduate to the `wasm4pm` execution engine.
449///
450/// # Examples
451///
452/// ```ignore
453/// use wasm4pm_compat::ids::EventTypeName;
454/// enum MyLog {}
455/// let place = EventTypeName::<MyLog>::from_static("place_order");
456/// assert_eq!(place.as_str(), "place_order");
457/// ```
458pub struct EventTypeName<K> {
459    label: Cow<'static, str>,
460    _kind: PhantomData<K>,
461}
462
463impl<K> EventTypeName<K> {
464    /// Wraps a `&'static str` label without allocation.
465    ///
466    /// # Examples
467    ///
468    /// ```ignore
469    /// use wasm4pm_compat::ids::EventTypeName;
470    /// enum MyLog {}
471    /// let t = EventTypeName::<MyLog>::from_static("place_order");
472    /// assert_eq!(t.as_str(), "place_order");
473    /// ```
474    #[inline]
475    pub fn from_static(label: &'static str) -> Self {
476        Self {
477            label: Cow::Borrowed(label),
478            _kind: PhantomData,
479        }
480    }
481
482    /// Wraps an owned `String` label, allocating on the heap.
483    ///
484    /// # Examples
485    ///
486    /// ```ignore
487    /// use wasm4pm_compat::ids::EventTypeName;
488    /// enum MyLog {}
489    /// let t = EventTypeName::<MyLog>::from_owned(String::from("ship_item"));
490    /// assert_eq!(t.as_str(), "ship_item");
491    /// ```
492    #[inline]
493    pub fn from_owned(label: String) -> Self {
494        Self {
495            label: Cow::Owned(label),
496            _kind: PhantomData,
497        }
498    }
499
500    /// Returns the string label.
501    ///
502    /// # Examples
503    ///
504    /// ```ignore
505    /// use wasm4pm_compat::ids::EventTypeName;
506    /// enum MyLog {}
507    /// assert_eq!(EventTypeName::<MyLog>::from_static("ship_item").as_str(), "ship_item");
508    /// ```
509    #[inline]
510    pub fn as_str(&self) -> &str {
511        &self.label
512    }
513}
514
515impl<K> Clone for EventTypeName<K> {
516    #[inline]
517    fn clone(&self) -> Self {
518        Self {
519            label: self.label.clone(),
520            _kind: PhantomData,
521        }
522    }
523}
524impl<K> PartialEq for EventTypeName<K> {
525    #[inline]
526    fn eq(&self, other: &Self) -> bool {
527        self.label == other.label
528    }
529}
530impl<K> Eq for EventTypeName<K> {}
531impl<K> core::hash::Hash for EventTypeName<K> {
532    #[inline]
533    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
534        self.label.hash(state);
535    }
536}
537impl<K> core::fmt::Debug for EventTypeName<K> {
538    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
539        f.debug_tuple("EventTypeName").field(&self.label).finish()
540    }
541}
542/// Displays the string label prefixed by the type name, e.g. `EventTypeName("place_order")`.
543impl<K> core::fmt::Display for EventTypeName<K> {
544    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
545        write!(f, "EventTypeName(\"{}\")", self.label)
546    }
547}
548impl<K> PartialOrd for EventTypeName<K> {
549    #[inline]
550    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
551        Some(self.cmp(other))
552    }
553}
554impl<K> Ord for EventTypeName<K> {
555    #[inline]
556    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
557        self.label.cmp(&other.label)
558    }
559}
560
561// ── id_of — phantom-typed marker constructor ──────────────────────────────────
562
563/// Constructs a [`TypedId`] value from its raw primitive, anchored to the
564/// id type's kind marker via turbofish.
565///
566/// # What this is
567///
568/// `id_of` is a zero-cost free function that is the *canonical* call-site form
569/// for building a typed id when the kind is known at the call site:
570///
571/// ```ignore
572/// use wasm4pm_compat::ids::{EventId, id_of};
573/// enum MyLog {}
574/// let ev = id_of::<EventId<MyLog>>(42u64);
575/// assert_eq!(ev.raw(), 42u64);
576/// ```
577///
578/// It is identical in behaviour to `T::new(raw)` but makes the intent explicit
579/// in code: *"I am constructing an id of this exact kind"*.
580///
581/// # What this is NOT
582///
583/// This is not a resolver. The constructed id names an entity; it does not
584/// validate that the entity exists in any log. Graduate to `wasm4pm` for that.
585///
586/// # When to graduate
587///
588/// When you need to resolve the id to the value it names, or validate link
589/// existence, move that logic to the `wasm4pm` execution engine.
590///
591/// # Examples
592///
593/// ```ignore
594/// use wasm4pm_compat::ids::{EventId, ObjectId, TraceId, id_of};
595/// enum MyLog {}
596/// let ev   = id_of::<EventId<MyLog>>(1u64);
597/// let obj  = id_of::<ObjectId<MyLog>>(2u64);
598/// let tr   = id_of::<TraceId<MyLog>>(3u64);
599/// assert_eq!(ev.raw(), 1u64);
600/// assert_eq!(obj.raw(), 2u64);
601/// assert_eq!(tr.raw(), 3u64);
602/// ```
603pub fn id_of<T: NewFromRaw>(raw: T::Raw) -> T {
604    T::new_from_raw(raw)
605}
606
607/// Supporting trait for [`id_of`]: lets the free function call the correct
608/// `new` constructor regardless of which typed-id newtype `T` is.
609///
610/// # What this is
611///
612/// `NewFromRaw` is a *sealed* companion trait implemented only by the numeric
613/// typed-id newtypes in this module. External code cannot implement it; it
614/// exists solely to make [`id_of`] generic over all typed-id kinds.
615///
616/// # What this is NOT
617///
618/// This trait is not part of the public extension surface. Do not implement it
619/// outside this module. Do not use it directly — use [`id_of`] instead.
620///
621/// # When to graduate
622///
623/// Never — this is a crate-internal plumbing trait with no engine concern.
624pub trait NewFromRaw: sealed::SealedId {
625    /// The underlying raw primitive (same as [`TypedId::Raw`]).
626    type Raw;
627    /// Constructs `Self` from a raw primitive. Identical to the concrete `new` fn.
628    fn new_from_raw(raw: Self::Raw) -> Self;
629}
630
631macro_rules! impl_new_from_raw {
632    ($name:ident, $raw:ty) => {
633        impl<K> NewFromRaw for $name<K> {
634            type Raw = $raw;
635            #[inline]
636            fn new_from_raw(raw: $raw) -> Self {
637                Self::new(raw)
638            }
639        }
640    };
641}
642
643impl_new_from_raw!(EventId, u64);
644impl_new_from_raw!(ObjectId, u64);
645impl_new_from_raw!(ActivityId, u32);
646impl_new_from_raw!(RelationId, u32);
647impl_new_from_raw!(TraceId, u64);
648impl_new_from_raw!(ObjectTypeId, u32);
649impl_new_from_raw!(EventTypeId, u32);
650impl_new_from_raw!(CaseId, u64);
651
652// ── From / Into / AsRef / FromStr for integer-backed id types ────────────────
653
654/// Expands `From<$raw>`, `Into<$raw>`, `AsRef<$raw>`, and `FromStr` for a
655/// `#[repr(transparent)]` id newtype.
656///
657/// `From` and its reciprocal `Into` are infallible by design — wrapping a raw
658/// integer never fails. `AsRef<$raw>` borrows the interior without copying.
659/// `FromStr` parses the decimal representation of the raw primitive; it is
660/// infallible in the sense that the only failure mode is a malformed integer
661/// string, which is surfaced via `$raw::from_str`.
662macro_rules! impl_id_conversions {
663    ($name:ident, $raw:ty) => {
664        impl<K> From<$raw> for $name<K> {
665            /// Wraps a raw primitive as a typed id. Infallible.
666            #[inline]
667            fn from(raw: $raw) -> Self {
668                Self::new(raw)
669            }
670        }
671
672        impl<K> From<$name<K>> for $raw {
673            /// Unwraps the typed id back to its raw primitive. Infallible.
674            #[inline]
675            fn from(id: $name<K>) -> $raw {
676                id.raw
677            }
678        }
679
680        impl<K> AsRef<$raw> for $name<K> {
681            /// Borrows the underlying raw value without copying.
682            #[inline]
683            fn as_ref(&self) -> &$raw {
684                &self.raw
685            }
686        }
687
688        impl<K> core::str::FromStr for $name<K> {
689            type Err = <$raw as core::str::FromStr>::Err;
690
691            /// Parses the decimal representation of the raw primitive.
692            ///
693            /// # Errors
694            ///
695            /// Returns the same error as `$raw::from_str` when the string is not
696            /// a valid decimal integer.
697            #[inline]
698            fn from_str(s: &str) -> Result<Self, Self::Err> {
699                s.parse::<$raw>().map(Self::new)
700            }
701        }
702    };
703}
704
705impl_id_conversions!(EventId, u64);
706impl_id_conversions!(ObjectId, u64);
707impl_id_conversions!(ActivityId, u32);
708impl_id_conversions!(RelationId, u32);
709impl_id_conversions!(TraceId, u64);
710impl_id_conversions!(ObjectTypeId, u32);
711impl_id_conversions!(EventTypeId, u32);
712impl_id_conversions!(CaseId, u64);
713
714// ── From / AsRef / FromStr for string-backed name types ──────────────────────
715
716impl<K> From<&'static str> for ObjectTypeName<K> {
717    /// Wraps a `&'static str` label without allocation. Infallible.
718    #[inline]
719    fn from(s: &'static str) -> Self {
720        Self::from_static(s)
721    }
722}
723
724impl<K> From<String> for ObjectTypeName<K> {
725    /// Wraps an owned `String` label, allocating on the heap. Infallible.
726    #[inline]
727    fn from(s: String) -> Self {
728        Self::from_owned(s)
729    }
730}
731
732impl<K> AsRef<str> for ObjectTypeName<K> {
733    /// Borrows the string label without allocating.
734    #[inline]
735    fn as_ref(&self) -> &str {
736        self.as_str()
737    }
738}
739
740impl<K> core::str::FromStr for ObjectTypeName<K> {
741    type Err = core::convert::Infallible;
742
743    /// Parses any string as an `ObjectTypeName`. Always succeeds.
744    #[inline]
745    fn from_str(s: &str) -> Result<Self, Self::Err> {
746        Ok(Self::from_owned(s.to_owned()))
747    }
748}
749
750impl<K> From<&'static str> for EventTypeName<K> {
751    /// Wraps a `&'static str` label without allocation. Infallible.
752    #[inline]
753    fn from(s: &'static str) -> Self {
754        Self::from_static(s)
755    }
756}
757
758impl<K> From<String> for EventTypeName<K> {
759    /// Wraps an owned `String` label, allocating on the heap. Infallible.
760    #[inline]
761    fn from(s: String) -> Self {
762        Self::from_owned(s)
763    }
764}
765
766impl<K> AsRef<str> for EventTypeName<K> {
767    /// Borrows the string label without allocating.
768    #[inline]
769    fn as_ref(&self) -> &str {
770        self.as_str()
771    }
772}
773
774impl<K> core::str::FromStr for EventTypeName<K> {
775    type Err = core::convert::Infallible;
776
777    /// Parses any string as an `EventTypeName`. Always succeeds.
778    #[inline]
779    fn from_str(s: &str) -> Result<Self, Self::Err> {
780        Ok(Self::from_owned(s.to_owned()))
781    }
782}