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