1use core::fmt;
2use std::{borrow::Cow, ops::Deref};
3
4use crate::{
5 components::{ComponentContext, ComponentType},
6 data_model::event::EventType,
7};
8
9const INVALID_COMPONENT_ID: &str =
10 "component IDs may only contain alphanumeric characters (a-z, A-Z, or 0-9), underscores, and hyphens";
11const INVALID_COMPONENT_OUTPUT_ID: &str = "component IDs may only contain alphanumeric characters (a-z, A-Z, or 0-9), underscores, hyphens, and an optional period";
12
13#[derive(Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
15pub struct ComponentId(Cow<'static, str>);
16
17impl TryFrom<&str> for ComponentId {
18 type Error = &'static str;
19
20 fn try_from(value: &str) -> Result<Self, Self::Error> {
21 if !validate_component_id(value, false) {
22 Err(INVALID_COMPONENT_ID)
23 } else {
24 Ok(Self(value.to_string().into()))
25 }
26 }
27}
28
29impl Deref for ComponentId {
30 type Target = str;
31
32 fn deref(&self) -> &Self::Target {
33 self.0.as_ref()
34 }
35}
36
37impl fmt::Display for ComponentId {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 self.0.fmt(f)
40 }
41}
42
43#[derive(Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
45pub struct ComponentOutputId(Cow<'static, str>);
46
47impl ComponentOutputId {
48 pub fn from_definition(component_id: ComponentId, output_def: &OutputDefinition) -> Result<Self, (String, String)> {
55 match output_def.output_name() {
56 None => Ok(Self(component_id.0)),
57 Some(output_name) => {
58 let output_id = format!("{}.{}", component_id.0, output_name);
59
60 if validate_component_id(&output_id, true) {
61 Ok(Self(output_id.into()))
62 } else {
63 Err((output_id, "invalid".to_string()))
65 }
66 }
67 }
68 }
69
70 pub fn component_id(&self) -> ComponentId {
72 if let Some((component_id, _)) = self.0.split_once('.') {
73 ComponentId(component_id.to_string().into())
74 } else {
75 ComponentId(self.0.clone())
76 }
77 }
78
79 pub fn output(&self) -> OutputName {
81 if let Some((_, output_name)) = self.0.split_once('.') {
82 OutputName::Given(output_name.to_string().into())
83 } else {
84 OutputName::Default
85 }
86 }
87
88 pub fn is_default(&self) -> bool {
90 self.0.split_once('.').is_none()
91 }
92}
93
94impl TryFrom<&str> for ComponentOutputId {
95 type Error = &'static str;
96
97 fn try_from(value: &str) -> Result<Self, Self::Error> {
98 if !validate_component_id(value, true) {
99 Err(INVALID_COMPONENT_OUTPUT_ID)
100 } else {
101 Ok(Self(value.to_string().into()))
102 }
103 }
104}
105
106impl fmt::Display for ComponentOutputId {
107 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108 self.0.fmt(f)
109 }
110}
111
112const fn validate_component_id(id: &str, as_output_id: bool) -> bool {
113 let id_bytes = id.as_bytes();
114
115 if id_bytes.is_empty() {
117 return false;
118 }
119
120 let mut idx = 0;
124 let end = id_bytes.len();
125 let mut separator_idx = end;
126 while idx < end {
127 let b = id_bytes[idx];
128 if !b.is_ascii_alphanumeric() && b != b'_' && b != b'-' {
129 if as_output_id && b == b'.' && separator_idx == end {
130 separator_idx = idx;
132 } else {
133 return false;
136 }
137 }
138
139 idx += 1;
140 }
141
142 if as_output_id && (separator_idx == 0 || separator_idx == end - 1) {
143 return false;
145 }
146
147 true
148}
149
150#[derive(Clone, Debug, Eq, Hash, PartialEq)]
157pub enum OutputName {
158 Default,
160
161 Given(Cow<'static, str>),
163}
164
165impl fmt::Display for OutputName {
166 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167 match self {
168 OutputName::Default => write!(f, "_default"),
169 OutputName::Given(name) => write!(f, "{}", name),
170 }
171 }
172}
173
174#[derive(Clone, Debug)]
179pub struct OutputDefinition {
180 name: OutputName,
181 event_ty: EventType,
182}
183
184impl OutputDefinition {
185 pub const fn default_output(event_ty: EventType) -> Self {
187 Self {
188 name: OutputName::Default,
189 event_ty,
190 }
191 }
192
193 pub fn named_output<S>(name: S, event_ty: EventType) -> Self
195 where
196 S: Into<Cow<'static, str>>,
197 {
198 Self {
199 name: OutputName::Given(name.into()),
200 event_ty,
201 }
202 }
203
204 pub fn output_name(&self) -> Option<&str> {
208 match &self.name {
209 OutputName::Default => None,
210 OutputName::Given(name) => Some(name.as_ref()),
211 }
212 }
213
214 pub fn event_ty(&self) -> EventType {
216 self.event_ty
217 }
218}
219
220#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
222pub struct TypedComponentId {
223 id: ComponentId,
224 ty: ComponentType,
225}
226
227impl TypedComponentId {
228 pub fn new(id: ComponentId, ty: ComponentType) -> Self {
230 Self { id, ty }
231 }
232
233 pub fn component_id(&self) -> &ComponentId {
235 &self.id
236 }
237
238 pub fn component_type(&self) -> ComponentType {
240 self.ty
241 }
242
243 pub fn component_context(&self) -> ComponentContext {
245 match self.ty {
246 ComponentType::Source => ComponentContext::source(self.id.clone()),
247 ComponentType::Transform => ComponentContext::transform(self.id.clone()),
248 ComponentType::Destination => ComponentContext::destination(self.id.clone()),
249 ComponentType::Encoder => ComponentContext::encoder(self.id.clone()),
250 ComponentType::Forwarder => ComponentContext::forwarder(self.id.clone()),
251 }
252 }
253
254 pub fn into_parts(self) -> (ComponentId, ComponentType, ComponentContext) {
256 let component_context = self.component_context();
257 (self.id, self.ty, component_context)
258 }
259}
260
261#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
263pub struct TypedComponentOutputId {
264 component_output: ComponentOutputId,
265 output_ty: EventType,
266}
267
268impl TypedComponentOutputId {
269 pub fn new(component_output: ComponentOutputId, output_ty: EventType) -> Self {
271 Self {
272 component_output,
273 output_ty,
274 }
275 }
276
277 pub fn component_output(&self) -> &ComponentOutputId {
279 &self.component_output
280 }
281
282 pub fn output_ty(&self) -> EventType {
284 self.output_ty
285 }
286}
287
288#[cfg(test)]
289mod tests {
290 use super::*;
291
292 #[test]
293 fn component_id() {
294 let id = ComponentId::try_from("component").unwrap();
295 assert_eq!(id, ComponentId::try_from("component").unwrap());
296 assert_eq!(&*id, "component");
297
298 let id = ComponentId::try_from("component_1").unwrap();
299 assert_eq!(id, ComponentId::try_from("component_1").unwrap());
300 assert_eq!(&*id, "component_1");
301 }
302
303 #[test]
304 fn component_id_invalid() {
305 assert!(ComponentId::try_from("").is_err());
306 assert!(ComponentId::try_from("non_alphanumeric_$#!").is_err());
307 assert!(ComponentId::try_from("cant_have_periods_for_non_component_output_id.foo").is_err());
308 }
309
310 #[test]
311 fn component_output_id_default() {
312 let id = ComponentOutputId::try_from("component").unwrap();
313 assert_eq!(id.component_id(), ComponentId::try_from("component").unwrap());
314 assert_eq!(id.output(), OutputName::Default);
315 assert!(id.is_default());
316 }
317
318 #[test]
319 fn component_output_id_named() {
320 let id = ComponentOutputId::try_from("component.metrics").unwrap();
321 assert_eq!(id.component_id(), ComponentId::try_from("component").unwrap());
322 assert_eq!(id.output(), OutputName::Given("metrics".into()));
323 assert!(!id.is_default());
324 }
325
326 #[test]
327 fn component_output_id_invalid() {
328 assert!(ComponentOutputId::try_from("").is_err());
329 assert!(ComponentOutputId::try_from("non_alphanumeric_$#!").is_err());
330 assert!(ComponentOutputId::try_from("too.many.periods").is_err());
331 assert!(ComponentOutputId::try_from(".one_side_of_named_output_is_empty").is_err());
332 assert!(ComponentOutputId::try_from("one_side_of_named_output_is_empty.").is_err());
333 }
334}