Skip to main content

obzenflow_core/event/identity/
writer_id.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2// SPDX-FileCopyrightText: 2025-2026 ObzenFlow Contributors
3// https://obzenflow.dev
4
5//! Unified writer ID that can represent either a Stage or System writer
6
7use crate::id::{StageId, SystemId};
8use serde::{Deserialize, Serialize};
9use std::fmt;
10
11/// Unified writer identifier that can be either a Stage or System
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
13#[serde(tag = "type", content = "id")]
14pub enum WriterId {
15    /// A stage writer
16    Stage(StageId),
17    /// A system component writer
18    System(SystemId),
19}
20
21impl WriterId {
22    /// Check if this is a stage writer
23    pub fn is_stage(&self) -> bool {
24        matches!(self, WriterId::Stage(_))
25    }
26
27    /// Check if this is a system writer
28    pub fn is_system(&self) -> bool {
29        matches!(self, WriterId::System(_))
30    }
31
32    /// Try to get as a stage ID
33    pub fn as_stage(&self) -> Option<&StageId> {
34        match self {
35            WriterId::Stage(id) => Some(id),
36            WriterId::System(_) => None,
37        }
38    }
39
40    /// Try to get as a system ID
41    pub fn as_system(&self) -> Option<&SystemId> {
42        match self {
43            WriterId::System(id) => Some(id),
44            WriterId::Stage(_) => None,
45        }
46    }
47
48    /// Get the underlying ULID regardless of type
49    pub fn as_ulid(&self) -> ulid::Ulid {
50        match self {
51            WriterId::Stage(id) => id.as_ulid(),
52            WriterId::System(id) => id.as_ulid(),
53        }
54    }
55}
56
57impl fmt::Display for WriterId {
58    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59        match self {
60            WriterId::Stage(id) => write!(f, "writer_{id}"),
61            WriterId::System(id) => write!(f, "writer_{id}"),
62        }
63    }
64}
65
66// From implementations for construction
67impl From<StageId> for WriterId {
68    fn from(stage_id: StageId) -> Self {
69        WriterId::Stage(stage_id)
70    }
71}
72
73impl From<SystemId> for WriterId {
74    fn from(system_id: SystemId) -> Self {
75        WriterId::System(system_id)
76    }
77}
78
79// TryFrom implementations for extraction
80impl TryFrom<WriterId> for StageId {
81    type Error = &'static str;
82
83    fn try_from(writer_id: WriterId) -> Result<Self, Self::Error> {
84        match writer_id {
85            WriterId::Stage(id) => Ok(id),
86            WriterId::System(_) => Err("WriterId is not a Stage"),
87        }
88    }
89}
90
91impl TryFrom<WriterId> for SystemId {
92    type Error = &'static str;
93
94    fn try_from(writer_id: WriterId) -> Result<Self, Self::Error> {
95        match writer_id {
96            WriterId::System(id) => Ok(id),
97            WriterId::Stage(_) => Err("WriterId is not a System"),
98        }
99    }
100}
101
102// From<ulid::Ulid> requires knowing the type, so we provide constructors instead
103impl WriterId {
104    /// Create a stage writer from a ULID
105    pub fn stage_from_ulid(ulid: ulid::Ulid) -> Self {
106        WriterId::Stage(StageId::from(ulid))
107    }
108
109    /// Create a system writer from a ULID
110    pub fn system_from_ulid(ulid: ulid::Ulid) -> Self {
111        WriterId::System(SystemId::from(ulid))
112    }
113}
114
115// Into<ulid::Ulid> for any WriterId
116impl From<WriterId> for ulid::Ulid {
117    fn from(writer_id: WriterId) -> Self {
118        writer_id.as_ulid()
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn test_writer_id_from_stage() {
128        let stage_id = StageId::new();
129        let writer_id = WriterId::from(stage_id);
130        assert!(writer_id.is_stage());
131        assert!(!writer_id.is_system());
132        assert_eq!(writer_id.as_stage(), Some(&stage_id));
133        assert_eq!(writer_id.as_system(), None);
134    }
135
136    #[test]
137    fn test_writer_id_from_system() {
138        let system_id = SystemId::new();
139        let writer_id = WriterId::from(system_id);
140        assert!(!writer_id.is_stage());
141        assert!(writer_id.is_system());
142        assert_eq!(writer_id.as_stage(), None);
143        assert_eq!(writer_id.as_system(), Some(&system_id));
144    }
145
146    #[test]
147    fn test_try_from_writer_id() {
148        let stage_id = StageId::new();
149        let writer_id = WriterId::from(stage_id);
150
151        // Should succeed for StageId
152        let extracted_stage: StageId = writer_id.try_into().unwrap();
153        assert_eq!(extracted_stage, stage_id);
154
155        // Should fail for SystemId
156        let result: Result<SystemId, _> = writer_id.try_into();
157        assert!(result.is_err());
158
159        let system_id = SystemId::new();
160        let writer_id = WriterId::from(system_id);
161
162        // Should succeed for SystemId
163        let extracted_system: SystemId = writer_id.try_into().unwrap();
164        assert_eq!(extracted_system, system_id);
165
166        // Should fail for StageId
167        let result: Result<StageId, _> = writer_id.try_into();
168        assert!(result.is_err());
169    }
170
171    #[test]
172    fn test_writer_id_ulid_conversion() {
173        let stage_id = StageId::new();
174        let writer_id = WriterId::from(stage_id);
175        let ulid: ulid::Ulid = writer_id.into();
176        assert_eq!(ulid, stage_id.as_ulid());
177
178        // Test constructors from ULID
179        let ulid = ulid::Ulid::new();
180        let stage_writer = WriterId::stage_from_ulid(ulid);
181        assert!(stage_writer.is_stage());
182
183        let system_writer = WriterId::system_from_ulid(ulid);
184        assert!(system_writer.is_system());
185    }
186
187    #[test]
188    fn test_writer_id_serde() {
189        let stage_writer = WriterId::from(StageId::new());
190        let json = serde_json::to_string(&stage_writer).unwrap();
191        let deserialized: WriterId = serde_json::from_str(&json).unwrap();
192        assert_eq!(stage_writer, deserialized);
193
194        let system_writer = WriterId::from(SystemId::new());
195        let json = serde_json::to_string(&system_writer).unwrap();
196        let deserialized: WriterId = serde_json::from_str(&json).unwrap();
197        assert_eq!(system_writer, deserialized);
198    }
199}