torvyn_types/
enums.rs

1//! Domain enumerations for the Torvyn runtime.
2//!
3//! Lightweight, `Copy` types for component roles, backpressure signals/policies,
4//! observability levels, severity levels, and copy reasons.
5
6use std::fmt;
7
8#[cfg(feature = "serde")]
9use serde::{Deserialize, Serialize};
10
11// ---------------------------------------------------------------------------
12// ComponentRole
13// ---------------------------------------------------------------------------
14
15/// The role of a component within a pipeline topology.
16///
17/// Per consolidated review (Doc 10, C02-7, C04-4): `ComponentRole` is the
18/// canonical name, replacing `NodeRole` (Doc 02) and `StageRole` (Doc 04).
19///
20/// # Examples
21/// ```
22/// use torvyn_types::ComponentRole;
23///
24/// let role = ComponentRole::Processor;
25/// assert_eq!(format!("{}", role), "Processor");
26/// ```
27#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
28#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
29pub enum ComponentRole {
30    /// Produces stream elements by pulling from an external data source.
31    Source,
32    /// Transforms stream elements (1:1 input-to-output mapping).
33    Processor,
34    /// Consumes stream elements by writing to an external destination.
35    Sink,
36    /// Evaluates whether elements pass a predicate (accept/reject, no new buffer).
37    Filter,
38    /// Routes elements to one or more named output ports.
39    Router,
40}
41
42impl ComponentRole {
43    /// Returns `true` if this role produces stream elements.
44    ///
45    /// # COLD PATH — called during topology validation.
46    #[inline]
47    pub const fn is_producer(&self) -> bool {
48        matches!(
49            self,
50            ComponentRole::Source | ComponentRole::Processor | ComponentRole::Router
51        )
52    }
53
54    /// Returns `true` if this role consumes stream elements.
55    ///
56    /// # COLD PATH — called during topology validation.
57    #[inline]
58    pub const fn is_consumer(&self) -> bool {
59        matches!(
60            self,
61            ComponentRole::Processor
62                | ComponentRole::Sink
63                | ComponentRole::Filter
64                | ComponentRole::Router
65        )
66    }
67}
68
69impl fmt::Display for ComponentRole {
70    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71        match self {
72            ComponentRole::Source => write!(f, "Source"),
73            ComponentRole::Processor => write!(f, "Processor"),
74            ComponentRole::Sink => write!(f, "Sink"),
75            ComponentRole::Filter => write!(f, "Filter"),
76            ComponentRole::Router => write!(f, "Router"),
77        }
78    }
79}
80
81// ---------------------------------------------------------------------------
82// BackpressureSignal
83// ---------------------------------------------------------------------------
84
85/// Backpressure signal from a consumer to the runtime.
86///
87/// Maps directly to the WIT `backpressure-signal` enum defined in
88/// `torvyn:streaming@0.1.0` (Doc 01, Section 3.1).
89///
90/// # Examples
91/// ```
92/// use torvyn_types::BackpressureSignal;
93///
94/// let signal = BackpressureSignal::Pause;
95/// assert!(signal.is_paused());
96/// ```
97#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
98#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
99pub enum BackpressureSignal {
100    /// Consumer is ready to accept more data. Normal operation.
101    Ready,
102    /// Consumer requests the producer to pause until further notice.
103    Pause,
104}
105
106impl BackpressureSignal {
107    /// Returns `true` if the consumer is requesting a pause.
108    ///
109    /// # HOT PATH — called per element after sink invocation.
110    #[inline]
111    pub const fn is_paused(&self) -> bool {
112        matches!(self, BackpressureSignal::Pause)
113    }
114}
115
116impl fmt::Display for BackpressureSignal {
117    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118        match self {
119            BackpressureSignal::Ready => write!(f, "Ready"),
120            BackpressureSignal::Pause => write!(f, "Pause"),
121        }
122    }
123}
124
125// ---------------------------------------------------------------------------
126// BackpressurePolicy
127// ---------------------------------------------------------------------------
128
129/// Policy governing what happens when a stream queue is full.
130///
131/// Configured per-stream or per-flow. The reactor enforces the chosen policy.
132///
133/// # Examples
134/// ```
135/// use torvyn_types::BackpressurePolicy;
136///
137/// let policy = BackpressurePolicy::BlockProducer;
138/// assert_eq!(format!("{}", policy), "BlockProducer");
139/// ```
140#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
141#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
142pub enum BackpressurePolicy {
143    /// Block the producer until space is available (default).
144    /// This is the safest option and prevents data loss.
145    #[default]
146    BlockProducer,
147    /// Drop the oldest element in the queue to make room for the new one.
148    /// Useful for real-time systems where freshness matters more than completeness.
149    DropOldest,
150    /// Drop the newest element (the one being produced) when the queue is full.
151    /// The producer continues without blocking; the new element is discarded.
152    DropNewest,
153    /// Return an error to the producer when the queue is full.
154    /// The producer must handle the error (retry, skip, or fail).
155    Error,
156}
157
158impl BackpressurePolicy {
159    /// Returns `true` if this policy can cause data loss.
160    ///
161    /// # COLD PATH — called during configuration validation.
162    #[inline]
163    pub const fn may_lose_data(&self) -> bool {
164        matches!(
165            self,
166            BackpressurePolicy::DropOldest | BackpressurePolicy::DropNewest
167        )
168    }
169}
170
171// LLI DEVIATION: Default derived via #[derive(Default)] + #[default] instead of manual impl per clippy.
172
173impl fmt::Display for BackpressurePolicy {
174    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
175        match self {
176            BackpressurePolicy::BlockProducer => write!(f, "BlockProducer"),
177            BackpressurePolicy::DropOldest => write!(f, "DropOldest"),
178            BackpressurePolicy::DropNewest => write!(f, "DropNewest"),
179            BackpressurePolicy::Error => write!(f, "Error"),
180        }
181    }
182}
183
184// ---------------------------------------------------------------------------
185// ObservabilityLevel
186// ---------------------------------------------------------------------------
187
188/// Observability collection level, configurable at runtime.
189///
190/// Per Doc 05, Section 1.4: three levels with explicit overhead budgets.
191/// Transitions are atomic (via `AtomicU8` in the collector).
192///
193/// # Examples
194/// ```
195/// use torvyn_types::ObservabilityLevel;
196///
197/// let level = ObservabilityLevel::Production;
198/// assert!(level.is_enabled());
199/// assert_eq!(level as u8, 1);
200/// ```
201#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
202#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
203#[repr(u8)]
204pub enum ObservabilityLevel {
205    /// Nothing collected. For bare-metal benchmarks. Overhead: 0%.
206    Off = 0,
207    /// Flow-level counters, latency histograms, error counts. Default.
208    /// Overhead: < 1% throughput, < 500ns per element.
209    #[default]
210    Production = 1,
211    /// All of Production + per-element spans, per-copy accounting,
212    /// per-backpressure events, queue depth snapshots.
213    /// Overhead: < 5% throughput, < 2us per element.
214    Diagnostic = 2,
215}
216
217impl ObservabilityLevel {
218    /// Returns `true` if any collection is enabled.
219    ///
220    /// # HOT PATH — checked per element to skip recording.
221    #[inline]
222    pub const fn is_enabled(&self) -> bool {
223        !matches!(self, ObservabilityLevel::Off)
224    }
225
226    /// Returns `true` if diagnostic-level detail is enabled.
227    ///
228    /// # HOT PATH — checked per element for detailed recording.
229    #[inline]
230    pub const fn is_diagnostic(&self) -> bool {
231        matches!(self, ObservabilityLevel::Diagnostic)
232    }
233
234    /// Convert from a raw `u8` value, returning `None` for invalid values.
235    #[inline]
236    pub const fn from_u8(value: u8) -> Option<Self> {
237        match value {
238            0 => Some(ObservabilityLevel::Off),
239            1 => Some(ObservabilityLevel::Production),
240            2 => Some(ObservabilityLevel::Diagnostic),
241            _ => None,
242        }
243    }
244}
245
246// LLI DEVIATION: Default derived via #[derive(Default)] + #[default] instead of manual impl per clippy.
247
248impl fmt::Display for ObservabilityLevel {
249    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
250        match self {
251            ObservabilityLevel::Off => write!(f, "Off"),
252            ObservabilityLevel::Production => write!(f, "Production"),
253            ObservabilityLevel::Diagnostic => write!(f, "Diagnostic"),
254        }
255    }
256}
257
258// ---------------------------------------------------------------------------
259// Severity
260// ---------------------------------------------------------------------------
261
262/// Log severity level for diagnostic events.
263///
264/// # Examples
265/// ```
266/// use torvyn_types::Severity;
267///
268/// let s = Severity::Warn;
269/// assert!(s >= Severity::Info);
270/// ```
271#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
272#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
273#[repr(u8)]
274pub enum Severity {
275    /// Fine-grained debugging information.
276    Trace = 0,
277    /// Developer-oriented debugging information.
278    Debug = 1,
279    /// Informational messages about normal operation.
280    Info = 2,
281    /// Potentially harmful situations that deserve attention.
282    Warn = 3,
283    /// Error events that may still allow the system to continue.
284    Error = 4,
285}
286
287impl fmt::Display for Severity {
288    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
289        match self {
290            Severity::Trace => write!(f, "TRACE"),
291            Severity::Debug => write!(f, "DEBUG"),
292            Severity::Info => write!(f, "INFO"),
293            Severity::Warn => write!(f, "WARN"),
294            Severity::Error => write!(f, "ERROR"),
295        }
296    }
297}
298
299// ---------------------------------------------------------------------------
300// CopyReason
301// ---------------------------------------------------------------------------
302
303/// The reason a data copy occurred, for copy accounting and observability.
304///
305/// Per Doc 05, Section 9.1 (`EventSink::record_copy`). This is the
306/// `torvyn-types` version; Doc 05 defines additional observability-specific
307/// variants. This crate provides the shared subset.
308///
309/// # Examples
310/// ```
311/// use torvyn_types::CopyReason;
312///
313/// let reason = CopyReason::HostToComponent;
314/// assert_eq!(format!("{}", reason), "HostToComponent");
315/// ```
316#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
317#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
318pub enum CopyReason {
319    /// Data must enter component linear memory for processing.
320    HostToComponent,
321    /// Data is extracted from component linear memory after processing.
322    ComponentToHost,
323    /// Data is transferred between components (involves a host intermediary).
324    CrossComponent,
325    /// Buffer contents are copied when returning to the pool.
326    PoolReturn,
327}
328
329impl fmt::Display for CopyReason {
330    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
331        match self {
332            CopyReason::HostToComponent => write!(f, "HostToComponent"),
333            CopyReason::ComponentToHost => write!(f, "ComponentToHost"),
334            CopyReason::CrossComponent => write!(f, "CrossComponent"),
335            CopyReason::PoolReturn => write!(f, "PoolReturn"),
336        }
337    }
338}
339
340// ---------------------------------------------------------------------------
341// Tests
342// ---------------------------------------------------------------------------
343
344#[cfg(test)]
345mod tests {
346    use super::*;
347
348    // --- ComponentRole ---
349
350    #[test]
351    fn test_component_role_display() {
352        assert_eq!(format!("{}", ComponentRole::Source), "Source");
353        assert_eq!(format!("{}", ComponentRole::Processor), "Processor");
354        assert_eq!(format!("{}", ComponentRole::Sink), "Sink");
355        assert_eq!(format!("{}", ComponentRole::Filter), "Filter");
356        assert_eq!(format!("{}", ComponentRole::Router), "Router");
357    }
358
359    #[test]
360    fn test_component_role_producer_consumer() {
361        assert!(ComponentRole::Source.is_producer());
362        assert!(!ComponentRole::Source.is_consumer());
363        assert!(ComponentRole::Processor.is_producer());
364        assert!(ComponentRole::Processor.is_consumer());
365        assert!(!ComponentRole::Sink.is_producer());
366        assert!(ComponentRole::Sink.is_consumer());
367        assert!(!ComponentRole::Filter.is_producer());
368        assert!(ComponentRole::Filter.is_consumer());
369        assert!(ComponentRole::Router.is_producer());
370        assert!(ComponentRole::Router.is_consumer());
371    }
372
373    // --- BackpressureSignal ---
374
375    #[test]
376    fn test_backpressure_signal_is_paused() {
377        assert!(!BackpressureSignal::Ready.is_paused());
378        assert!(BackpressureSignal::Pause.is_paused());
379    }
380
381    // --- BackpressurePolicy ---
382
383    #[test]
384    fn test_backpressure_policy_default() {
385        assert_eq!(
386            BackpressurePolicy::default(),
387            BackpressurePolicy::BlockProducer
388        );
389    }
390
391    #[test]
392    fn test_backpressure_policy_may_lose_data() {
393        assert!(!BackpressurePolicy::BlockProducer.may_lose_data());
394        assert!(BackpressurePolicy::DropOldest.may_lose_data());
395        assert!(BackpressurePolicy::DropNewest.may_lose_data());
396        assert!(!BackpressurePolicy::Error.may_lose_data());
397    }
398
399    // --- ObservabilityLevel ---
400
401    #[test]
402    fn test_observability_level_ordering() {
403        assert!(ObservabilityLevel::Off < ObservabilityLevel::Production);
404        assert!(ObservabilityLevel::Production < ObservabilityLevel::Diagnostic);
405    }
406
407    #[test]
408    fn test_observability_level_is_enabled() {
409        assert!(!ObservabilityLevel::Off.is_enabled());
410        assert!(ObservabilityLevel::Production.is_enabled());
411        assert!(ObservabilityLevel::Diagnostic.is_enabled());
412    }
413
414    #[test]
415    fn test_observability_level_is_diagnostic() {
416        assert!(!ObservabilityLevel::Off.is_diagnostic());
417        assert!(!ObservabilityLevel::Production.is_diagnostic());
418        assert!(ObservabilityLevel::Diagnostic.is_diagnostic());
419    }
420
421    #[test]
422    fn test_observability_level_from_u8() {
423        assert_eq!(
424            ObservabilityLevel::from_u8(0),
425            Some(ObservabilityLevel::Off)
426        );
427        assert_eq!(
428            ObservabilityLevel::from_u8(1),
429            Some(ObservabilityLevel::Production)
430        );
431        assert_eq!(
432            ObservabilityLevel::from_u8(2),
433            Some(ObservabilityLevel::Diagnostic)
434        );
435        assert_eq!(ObservabilityLevel::from_u8(3), None);
436    }
437
438    #[test]
439    fn test_observability_level_default() {
440        assert_eq!(
441            ObservabilityLevel::default(),
442            ObservabilityLevel::Production
443        );
444    }
445
446    #[test]
447    fn test_observability_level_repr_u8() {
448        assert_eq!(ObservabilityLevel::Off as u8, 0);
449        assert_eq!(ObservabilityLevel::Production as u8, 1);
450        assert_eq!(ObservabilityLevel::Diagnostic as u8, 2);
451    }
452
453    // --- Severity ---
454
455    #[test]
456    fn test_severity_ordering() {
457        assert!(Severity::Trace < Severity::Debug);
458        assert!(Severity::Debug < Severity::Info);
459        assert!(Severity::Info < Severity::Warn);
460        assert!(Severity::Warn < Severity::Error);
461    }
462
463    #[test]
464    fn test_severity_display() {
465        assert_eq!(format!("{}", Severity::Trace), "TRACE");
466        assert_eq!(format!("{}", Severity::Error), "ERROR");
467    }
468
469    // --- CopyReason ---
470
471    #[test]
472    fn test_copy_reason_display() {
473        assert_eq!(
474            format!("{}", CopyReason::HostToComponent),
475            "HostToComponent"
476        );
477        assert_eq!(
478            format!("{}", CopyReason::ComponentToHost),
479            "ComponentToHost"
480        );
481        assert_eq!(format!("{}", CopyReason::CrossComponent), "CrossComponent");
482        assert_eq!(format!("{}", CopyReason::PoolReturn), "PoolReturn");
483    }
484}