torvyn_types/
identity.rs

1//! Identity types for the Torvyn runtime.
2//!
3//! These are the foundational identifier types used by every subsystem.
4//! All identity types are `Copy`, `Eq`, `Hash`, and cheaply comparable.
5
6use std::fmt;
7
8#[cfg(feature = "serde")]
9use serde::{Deserialize, Serialize};
10
11// ---------------------------------------------------------------------------
12// ComponentTypeId
13// ---------------------------------------------------------------------------
14
15/// Content-addressed identifier for a compiled component artifact.
16///
17/// Derived from the SHA-256 hash of the component binary. Two components
18/// compiled from the same source with the same toolchain produce the same
19/// `ComponentTypeId`. Used for compilation caching and artifact deduplication.
20///
21/// # Invariants
22/// - The inner `[u8; 32]` is always a valid SHA-256 digest.
23/// - Two distinct component binaries must never share a `ComponentTypeId`
24///   (guaranteed by SHA-256 collision resistance).
25///
26/// # Examples
27/// ```
28/// use torvyn_types::ComponentTypeId;
29///
30/// let hash = [0xab; 32];
31/// let id = ComponentTypeId::new(hash);
32/// assert_eq!(id.as_bytes(), &[0xab; 32]);
33/// ```
34#[derive(Clone, Copy, PartialEq, Eq, Hash)]
35#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
36pub struct ComponentTypeId([u8; 32]);
37
38impl ComponentTypeId {
39    /// Create a new `ComponentTypeId` from a SHA-256 digest.
40    ///
41    /// # COLD PATH — called during component compilation/loading.
42    #[inline]
43    pub const fn new(hash: [u8; 32]) -> Self {
44        Self(hash)
45    }
46
47    /// Returns the raw bytes of the content hash.
48    #[inline]
49    pub const fn as_bytes(&self) -> &[u8; 32] {
50        &self.0
51    }
52
53    /// Returns a zero-valued `ComponentTypeId`, useful as a sentinel or default.
54    #[inline]
55    pub const fn zero() -> Self {
56        Self([0u8; 32])
57    }
58}
59
60impl fmt::Debug for ComponentTypeId {
61    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62        write!(f, "ComponentTypeId({})", self)
63    }
64}
65
66impl fmt::Display for ComponentTypeId {
67    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68        // Display first 8 bytes as hex for readability (16 hex chars)
69        for byte in &self.0[..8] {
70            write!(f, "{:02x}", byte)?;
71        }
72        write!(f, "\u{2026}")
73    }
74}
75
76// ---------------------------------------------------------------------------
77// ComponentInstanceId
78// ---------------------------------------------------------------------------
79
80/// Runtime identity for a component instance within a host.
81///
82/// Assigned by the host at instantiation time. Monotonically increasing
83/// within a single host process lifetime. Not stable across restarts.
84///
85/// # Invariants
86/// - Unique within a single host process.
87/// - Never reused during the lifetime of a host process.
88///
89/// # Examples
90/// ```
91/// use torvyn_types::ComponentInstanceId;
92///
93/// let id = ComponentInstanceId::new(42);
94/// assert_eq!(id.as_u64(), 42);
95/// assert_eq!(format!("{}", id), "component-42");
96/// ```
97#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
98#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
99pub struct ComponentInstanceId(u64);
100
101impl ComponentInstanceId {
102    /// Create a new `ComponentInstanceId`.
103    ///
104    /// # COLD PATH — called during component instantiation.
105    #[inline]
106    pub const fn new(id: u64) -> Self {
107        Self(id)
108    }
109
110    /// Returns the raw `u64` value.
111    #[inline]
112    pub const fn as_u64(&self) -> u64 {
113        self.0
114    }
115}
116
117impl fmt::Debug for ComponentInstanceId {
118    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119        write!(f, "ComponentInstanceId({})", self.0)
120    }
121}
122
123impl fmt::Display for ComponentInstanceId {
124    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125        write!(f, "component-{}", self.0)
126    }
127}
128
129impl From<u64> for ComponentInstanceId {
130    #[inline]
131    fn from(id: u64) -> Self {
132        Self(id)
133    }
134}
135
136/// Type alias for `ComponentInstanceId`.
137///
138/// Per consolidated review (Doc 10, Section 7.2): `ComponentId` is the
139/// canonical short name used throughout the runtime. It is an alias for
140/// `ComponentInstanceId` to resolve naming conflicts between Doc 02, 03, and 04.
141pub type ComponentId = ComponentInstanceId;
142
143// ---------------------------------------------------------------------------
144// FlowId
145// ---------------------------------------------------------------------------
146
147/// Unique identifier for a flow (pipeline execution instance).
148///
149/// Assigned by the reactor when a flow is created. Monotonically increasing.
150/// Not reused within a single host process lifetime.
151///
152/// # Invariants
153/// - Unique within a single host process.
154/// - Never reused.
155///
156/// # Examples
157/// ```
158/// use torvyn_types::FlowId;
159///
160/// let id = FlowId::new(7);
161/// assert_eq!(id.as_u64(), 7);
162/// assert_eq!(format!("{}", id), "flow-7");
163/// ```
164#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
165#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
166pub struct FlowId(u64);
167
168impl FlowId {
169    /// Create a new `FlowId`.
170    ///
171    /// # COLD PATH — called during flow creation.
172    #[inline]
173    pub const fn new(id: u64) -> Self {
174        Self(id)
175    }
176
177    /// Returns the raw `u64` value.
178    #[inline]
179    pub const fn as_u64(&self) -> u64 {
180        self.0
181    }
182}
183
184impl fmt::Debug for FlowId {
185    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
186        write!(f, "FlowId({})", self.0)
187    }
188}
189
190impl fmt::Display for FlowId {
191    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
192        write!(f, "flow-{}", self.0)
193    }
194}
195
196impl From<u64> for FlowId {
197    #[inline]
198    fn from(id: u64) -> Self {
199        Self(id)
200    }
201}
202
203// ---------------------------------------------------------------------------
204// StreamId
205// ---------------------------------------------------------------------------
206
207/// Unique identifier for a stream connection within the reactor.
208///
209/// A stream connects two components in a pipeline. Each stream has a
210/// bounded queue and backpressure policy.
211///
212/// # Invariants
213/// - Unique within a single host process.
214///
215/// # Examples
216/// ```
217/// use torvyn_types::StreamId;
218///
219/// let id = StreamId::new(3);
220/// assert_eq!(id.as_u64(), 3);
221/// assert_eq!(format!("{}", id), "stream-3");
222/// ```
223#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
224#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
225pub struct StreamId(u64);
226
227impl StreamId {
228    /// Create a new `StreamId`.
229    ///
230    /// # COLD PATH — called during flow construction.
231    #[inline]
232    pub const fn new(id: u64) -> Self {
233        Self(id)
234    }
235
236    /// Returns the raw `u64` value.
237    #[inline]
238    pub const fn as_u64(&self) -> u64 {
239        self.0
240    }
241}
242
243impl fmt::Debug for StreamId {
244    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
245        write!(f, "StreamId({})", self.0)
246    }
247}
248
249impl fmt::Display for StreamId {
250    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
251        write!(f, "stream-{}", self.0)
252    }
253}
254
255impl From<u64> for StreamId {
256    #[inline]
257    fn from(id: u64) -> Self {
258        Self(id)
259    }
260}
261
262// ---------------------------------------------------------------------------
263// ResourceId
264// ---------------------------------------------------------------------------
265
266/// Generational index into the resource table.
267///
268/// Combines a slot index with a generation counter to prevent ABA problems.
269/// When a resource is freed and its slot reused, the generation is incremented.
270/// Any handle holding the old generation will fail validation.
271///
272/// # Invariants
273/// - `index` identifies the slot in the resource table.
274/// - `generation` is incremented each time the slot is reused.
275/// - A handle is valid only if its generation matches the slot's current generation.
276///
277/// # Examples
278/// ```
279/// use torvyn_types::ResourceId;
280///
281/// let id = ResourceId::new(10, 1);
282/// assert_eq!(id.index(), 10);
283/// assert_eq!(id.generation(), 1);
284/// assert_eq!(format!("{}", id), "resource-10:g1");
285/// ```
286#[derive(Clone, Copy, PartialEq, Eq, Hash)]
287#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
288pub struct ResourceId {
289    index: u32,
290    generation: u32,
291}
292
293impl ResourceId {
294    /// Create a new `ResourceId`.
295    ///
296    /// # HOT PATH — called during resource allocation.
297    #[inline]
298    pub const fn new(index: u32, generation: u32) -> Self {
299        Self { index, generation }
300    }
301
302    /// Returns the slot index.
303    #[inline]
304    pub const fn index(&self) -> u32 {
305        self.index
306    }
307
308    /// Returns the generation counter.
309    #[inline]
310    pub const fn generation(&self) -> u32 {
311        self.generation
312    }
313
314    /// Returns a new `ResourceId` with the same index but incremented generation.
315    ///
316    /// # HOT PATH — called when a resource slot is reused.
317    #[inline]
318    pub const fn next_generation(&self) -> Self {
319        Self {
320            index: self.index,
321            generation: self.generation.wrapping_add(1),
322        }
323    }
324}
325
326impl fmt::Debug for ResourceId {
327    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
328        write!(f, "ResourceId({}, gen={})", self.index, self.generation)
329    }
330}
331
332impl fmt::Display for ResourceId {
333    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
334        write!(f, "resource-{}:g{}", self.index, self.generation)
335    }
336}
337
338// ---------------------------------------------------------------------------
339// BufferHandle
340// ---------------------------------------------------------------------------
341
342/// Typed wrapper for buffer resources, built on `ResourceId`.
343///
344/// Provides type safety: you cannot accidentally pass a `BufferHandle` where
345/// a raw `ResourceId` for a different resource kind is expected.
346///
347/// # Invariants
348/// - The inner `ResourceId` must refer to a buffer slot in the resource table.
349///
350/// # Examples
351/// ```
352/// use torvyn_types::{BufferHandle, ResourceId};
353///
354/// let rid = ResourceId::new(5, 0);
355/// let handle = BufferHandle::new(rid);
356/// assert_eq!(handle.resource_id().index(), 5);
357/// ```
358#[derive(Clone, Copy, PartialEq, Eq, Hash)]
359#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
360pub struct BufferHandle(ResourceId);
361
362impl BufferHandle {
363    /// Create a new `BufferHandle` from a `ResourceId`.
364    ///
365    /// # HOT PATH — called during buffer allocation.
366    #[inline]
367    pub const fn new(resource_id: ResourceId) -> Self {
368        Self(resource_id)
369    }
370
371    /// Returns the underlying `ResourceId`.
372    #[inline]
373    pub const fn resource_id(&self) -> ResourceId {
374        self.0
375    }
376}
377
378impl fmt::Debug for BufferHandle {
379    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
380        write!(f, "BufferHandle({})", self.0)
381    }
382}
383
384impl fmt::Display for BufferHandle {
385    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
386        write!(f, "buffer-{}:g{}", self.0.index(), self.0.generation())
387    }
388}
389
390impl From<ResourceId> for BufferHandle {
391    #[inline]
392    fn from(id: ResourceId) -> Self {
393        Self(id)
394    }
395}
396
397// ---------------------------------------------------------------------------
398// TraceId
399// ---------------------------------------------------------------------------
400
401/// W3C Trace Context trace ID (128-bit / 16 bytes).
402///
403/// Used for distributed trace correlation across pipeline components.
404/// Formatted as 32 lowercase hex characters per the W3C specification.
405///
406/// # Invariants
407/// - An all-zero trace ID is considered invalid (per W3C spec).
408///
409/// # Examples
410/// ```
411/// use torvyn_types::TraceId;
412///
413/// let id = TraceId::new([0x4b, 0xf9, 0x2f, 0x35, 0x77, 0xb3, 0x4d, 0xa6,
414///                        0xa3, 0xce, 0x92, 0x9d, 0x0e, 0x0e, 0x47, 0x36]);
415/// assert!(id.is_valid());
416/// assert_eq!(id.to_string().len(), 32);
417/// ```
418#[derive(Clone, Copy, PartialEq, Eq, Hash)]
419#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
420pub struct TraceId([u8; 16]);
421
422impl TraceId {
423    /// Create a new `TraceId` from raw bytes.
424    ///
425    /// # COLD PATH — called during flow creation or trace context propagation.
426    #[inline]
427    pub const fn new(bytes: [u8; 16]) -> Self {
428        Self(bytes)
429    }
430
431    /// Returns the raw bytes.
432    #[inline]
433    pub const fn as_bytes(&self) -> &[u8; 16] {
434        &self.0
435    }
436
437    /// Returns `true` if this trace ID is valid (non-zero per W3C spec).
438    #[inline]
439    pub fn is_valid(&self) -> bool {
440        self.0 != [0u8; 16]
441    }
442
443    /// Returns an invalid (all-zero) trace ID, indicating tracing is disabled.
444    #[inline]
445    pub const fn invalid() -> Self {
446        Self([0u8; 16])
447    }
448}
449
450impl fmt::Debug for TraceId {
451    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
452        write!(f, "TraceId({})", self)
453    }
454}
455
456impl fmt::Display for TraceId {
457    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
458        for byte in &self.0 {
459            write!(f, "{:02x}", byte)?;
460        }
461        Ok(())
462    }
463}
464
465// ---------------------------------------------------------------------------
466// SpanId
467// ---------------------------------------------------------------------------
468
469/// W3C Trace Context span ID (64-bit / 8 bytes).
470///
471/// Identifies a specific span within a trace. Formatted as 16 lowercase
472/// hex characters per the W3C specification.
473///
474/// # Invariants
475/// - An all-zero span ID is considered invalid (per W3C spec).
476///
477/// # Examples
478/// ```
479/// use torvyn_types::SpanId;
480///
481/// let id = SpanId::new([0x00, 0xf0, 0x67, 0xaa, 0x0b, 0xa9, 0x02, 0xb7]);
482/// assert!(id.is_valid());
483/// assert_eq!(id.to_string().len(), 16);
484/// ```
485#[derive(Clone, Copy, PartialEq, Eq, Hash)]
486#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
487pub struct SpanId([u8; 8]);
488
489impl SpanId {
490    /// Create a new `SpanId` from raw bytes.
491    ///
492    /// # WARM PATH — called per span creation.
493    #[inline]
494    pub const fn new(bytes: [u8; 8]) -> Self {
495        Self(bytes)
496    }
497
498    /// Returns the raw bytes.
499    #[inline]
500    pub const fn as_bytes(&self) -> &[u8; 8] {
501        &self.0
502    }
503
504    /// Returns `true` if this span ID is valid (non-zero per W3C spec).
505    #[inline]
506    pub fn is_valid(&self) -> bool {
507        self.0 != [0u8; 8]
508    }
509
510    /// Returns an invalid (all-zero) span ID.
511    #[inline]
512    pub const fn invalid() -> Self {
513        Self([0u8; 8])
514    }
515}
516
517impl fmt::Debug for SpanId {
518    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
519        write!(f, "SpanId({})", self)
520    }
521}
522
523impl fmt::Display for SpanId {
524    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
525        for byte in &self.0 {
526            write!(f, "{:02x}", byte)?;
527        }
528        Ok(())
529    }
530}
531
532// ---------------------------------------------------------------------------
533// Tests
534// ---------------------------------------------------------------------------
535
536#[cfg(test)]
537mod tests {
538    use super::*;
539
540    // --- ComponentTypeId ---
541
542    #[test]
543    fn test_component_type_id_new_and_bytes() {
544        let hash = [0xab; 32];
545        let id = ComponentTypeId::new(hash);
546        assert_eq!(id.as_bytes(), &[0xab; 32]);
547    }
548
549    #[test]
550    fn test_component_type_id_zero() {
551        let id = ComponentTypeId::zero();
552        assert_eq!(id.as_bytes(), &[0u8; 32]);
553    }
554
555    #[test]
556    fn test_component_type_id_equality() {
557        let a = ComponentTypeId::new([1; 32]);
558        let b = ComponentTypeId::new([1; 32]);
559        let c = ComponentTypeId::new([2; 32]);
560        assert_eq!(a, b);
561        assert_ne!(a, c);
562    }
563
564    #[test]
565    fn test_component_type_id_display_truncated() {
566        let id = ComponentTypeId::new([0xab; 32]);
567        let display = format!("{}", id);
568        assert!(display.starts_with("abababab"));
569        assert!(display.ends_with('\u{2026}'));
570    }
571
572    #[test]
573    fn test_component_type_id_hash_consistency() {
574        use std::collections::HashSet;
575        let mut set = HashSet::new();
576        let id = ComponentTypeId::new([0xcd; 32]);
577        set.insert(id);
578        assert!(set.contains(&ComponentTypeId::new([0xcd; 32])));
579    }
580
581    // --- ComponentInstanceId ---
582
583    #[test]
584    fn test_component_instance_id_new_and_value() {
585        let id = ComponentInstanceId::new(42);
586        assert_eq!(id.as_u64(), 42);
587    }
588
589    #[test]
590    fn test_component_instance_id_display() {
591        let id = ComponentInstanceId::new(42);
592        assert_eq!(format!("{}", id), "component-42");
593    }
594
595    #[test]
596    fn test_component_instance_id_ordering() {
597        let a = ComponentInstanceId::new(1);
598        let b = ComponentInstanceId::new(2);
599        assert!(a < b);
600    }
601
602    #[test]
603    fn test_component_instance_id_from_u64() {
604        let id: ComponentInstanceId = 99u64.into();
605        assert_eq!(id.as_u64(), 99);
606    }
607
608    #[test]
609    fn test_component_id_alias() {
610        let id: ComponentId = ComponentInstanceId::new(10);
611        assert_eq!(id.as_u64(), 10);
612    }
613
614    // --- FlowId ---
615
616    #[test]
617    fn test_flow_id_new_and_value() {
618        let id = FlowId::new(7);
619        assert_eq!(id.as_u64(), 7);
620    }
621
622    #[test]
623    fn test_flow_id_display() {
624        let id = FlowId::new(7);
625        assert_eq!(format!("{}", id), "flow-7");
626    }
627
628    #[test]
629    fn test_flow_id_ordering() {
630        let a = FlowId::new(1);
631        let b = FlowId::new(2);
632        assert!(a < b);
633    }
634
635    #[test]
636    fn test_flow_id_from_u64() {
637        let id: FlowId = 55u64.into();
638        assert_eq!(id.as_u64(), 55);
639    }
640
641    // --- StreamId ---
642
643    #[test]
644    fn test_stream_id_new_and_value() {
645        let id = StreamId::new(3);
646        assert_eq!(id.as_u64(), 3);
647    }
648
649    #[test]
650    fn test_stream_id_display() {
651        let id = StreamId::new(3);
652        assert_eq!(format!("{}", id), "stream-3");
653    }
654
655    // --- ResourceId ---
656
657    #[test]
658    fn test_resource_id_new_and_fields() {
659        let id = ResourceId::new(10, 1);
660        assert_eq!(id.index(), 10);
661        assert_eq!(id.generation(), 1);
662    }
663
664    #[test]
665    fn test_resource_id_display() {
666        let id = ResourceId::new(10, 1);
667        assert_eq!(format!("{}", id), "resource-10:g1");
668    }
669
670    #[test]
671    fn test_resource_id_next_generation() {
672        let id = ResourceId::new(5, 0);
673        let next = id.next_generation();
674        assert_eq!(next.index(), 5);
675        assert_eq!(next.generation(), 1);
676    }
677
678    #[test]
679    fn test_resource_id_generation_wraps() {
680        let id = ResourceId::new(0, u32::MAX);
681        let next = id.next_generation();
682        assert_eq!(next.generation(), 0);
683    }
684
685    #[test]
686    fn test_resource_id_different_generations_not_equal() {
687        let a = ResourceId::new(5, 0);
688        let b = ResourceId::new(5, 1);
689        assert_ne!(a, b);
690    }
691
692    // --- BufferHandle ---
693
694    #[test]
695    fn test_buffer_handle_new_and_resource_id() {
696        let rid = ResourceId::new(5, 0);
697        let handle = BufferHandle::new(rid);
698        assert_eq!(handle.resource_id(), rid);
699    }
700
701    #[test]
702    fn test_buffer_handle_display() {
703        let handle = BufferHandle::new(ResourceId::new(5, 2));
704        assert_eq!(format!("{}", handle), "buffer-5:g2");
705    }
706
707    #[test]
708    fn test_buffer_handle_from_resource_id() {
709        let rid = ResourceId::new(3, 1);
710        let handle: BufferHandle = rid.into();
711        assert_eq!(handle.resource_id(), rid);
712    }
713
714    // --- TraceId ---
715
716    #[test]
717    fn test_trace_id_valid() {
718        let id = TraceId::new([1; 16]);
719        assert!(id.is_valid());
720    }
721
722    #[test]
723    fn test_trace_id_invalid_zero() {
724        let id = TraceId::invalid();
725        assert!(!id.is_valid());
726    }
727
728    #[test]
729    fn test_trace_id_display_length() {
730        let id = TraceId::new([0xab; 16]);
731        assert_eq!(id.to_string().len(), 32); // 16 bytes * 2 hex chars
732    }
733
734    #[test]
735    fn test_trace_id_display_value() {
736        let id = TraceId::new([
737            0x4b, 0xf9, 0x2f, 0x35, 0x77, 0xb3, 0x4d, 0xa6, 0xa3, 0xce, 0x92, 0x9d, 0x0e, 0x0e,
738            0x47, 0x36,
739        ]);
740        assert_eq!(id.to_string(), "4bf92f3577b34da6a3ce929d0e0e4736");
741    }
742
743    // --- SpanId ---
744
745    #[test]
746    fn test_span_id_valid() {
747        let id = SpanId::new([1; 8]);
748        assert!(id.is_valid());
749    }
750
751    #[test]
752    fn test_span_id_invalid_zero() {
753        let id = SpanId::invalid();
754        assert!(!id.is_valid());
755    }
756
757    #[test]
758    fn test_span_id_display_length() {
759        let id = SpanId::new([0xab; 8]);
760        assert_eq!(id.to_string().len(), 16); // 8 bytes * 2 hex chars
761    }
762}