1#![allow(warnings)]
2use std::{fmt, num::NonZeroU32, sync::Arc};
5
6use indexmap::Equivalent;
7use saluki_common::hash::hash_single_fast;
8use serde::{Deserialize, Serialize};
9use stringtheory::MetaString;
10use tracing::warn;
11
12use crate::tags::{SharedTagSet, Tag};
13
14#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
16#[serde(try_from = "String")]
17pub enum OriginTagCardinality {
18 None,
22
23 Low,
28
29 Orchestrator,
34
35 High,
40}
41
42impl OriginTagCardinality {
43 pub const fn as_str(&self) -> &'static str {
45 match self {
46 Self::None => "none",
47 Self::Low => "low",
48 Self::Orchestrator => "orchestrator",
49 Self::High => "high",
50 }
51 }
52}
53
54impl TryFrom<&str> for OriginTagCardinality {
55 type Error = String;
56
57 fn try_from(value: &str) -> Result<Self, Self::Error> {
58 match value {
62 "none" => Ok(Self::None),
63 "low" => Ok(Self::Low),
64 "high" => Ok(Self::High),
65 "orch" | "orchestrator" => Ok(Self::Orchestrator),
66 other if other.eq_ignore_ascii_case("none") => Ok(Self::None),
67 other if other.eq_ignore_ascii_case("low") => Ok(Self::Low),
68 other if other.eq_ignore_ascii_case("high") => Ok(Self::High),
69 other if other.eq_ignore_ascii_case("orch") || other.eq_ignore_ascii_case("orchestrator") => {
70 Ok(Self::Orchestrator)
71 }
72 other => Err(format!("unknown tag cardinality type '{}'", other)),
73 }
74 }
75}
76
77impl TryFrom<String> for OriginTagCardinality {
78 type Error = String;
79
80 fn try_from(value: String) -> Result<Self, Self::Error> {
81 Self::try_from(value.as_str())
82 }
83}
84
85impl fmt::Display for OriginTagCardinality {
86 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87 match self {
88 Self::None => write!(f, "none"),
89 Self::Low => write!(f, "low"),
90 Self::Orchestrator => write!(f, "orchestrator"),
91 Self::High => write!(f, "high"),
92 }
93 }
94}
95
96#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
107pub struct RawOrigin<'a> {
108 process_id: Option<NonZeroU32>,
110
111 local_data: Option<&'a str>,
116
117 pod_uid: Option<&'a str>,
121
122 cardinality: Option<OriginTagCardinality>,
126
127 external_data: Option<RawExternalData<'a>>,
131}
132
133impl<'a> RawOrigin<'a> {
134 pub fn is_empty(&self) -> bool {
136 self.process_id.is_none()
137 && self.local_data.is_none()
138 && self.pod_uid.is_none()
139 && self.cardinality.is_none()
140 && self.external_data.is_none()
141 }
142
143 pub fn clear_process_id(&mut self) {
145 self.process_id = None;
146 }
147
148 pub fn set_process_id(&mut self, process_id: u32) {
152 self.process_id = NonZeroU32::new(process_id);
153 }
154
155 pub fn process_id(&self) -> Option<u32> {
157 self.process_id.map(NonZeroU32::get)
158 }
159
160 pub fn set_local_data(&mut self, local_data: impl Into<Option<&'a str>>) {
162 self.local_data = local_data.into();
163 }
164
165 pub fn local_data(&self) -> Option<&str> {
167 self.local_data
168 }
169
170 pub fn set_pod_uid(&mut self, pod_uid: impl Into<Option<&'a str>>) {
172 self.pod_uid = pod_uid.into();
173 }
174
175 pub fn pod_uid(&self) -> Option<&str> {
177 self.pod_uid
178 }
179
180 pub fn set_cardinality(&mut self, cardinality: impl Into<Option<OriginTagCardinality>>) {
182 self.cardinality = cardinality.into();
183 }
184
185 pub fn cardinality(&self) -> Option<OriginTagCardinality> {
187 self.cardinality.as_ref().copied()
188 }
189
190 pub fn set_external_data(&mut self, external_data: impl Into<Option<&'a str>>) {
192 self.external_data = external_data.into().and_then(RawExternalData::try_from_str);
193 }
194
195 pub fn external_data(&self) -> Option<&RawExternalData<'a>> {
197 self.external_data.as_ref()
198 }
199}
200
201impl fmt::Display for RawOrigin<'_> {
202 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203 let mut has_written = false;
204
205 write!(f, "RawOrigin(")?;
206
207 if let Some(process_id) = self.process_id {
208 write!(f, "process_id={}", process_id)?;
209 }
210
211 if let Some(local_data) = self.local_data {
212 if has_written {
213 write!(f, " ")?;
214 } else {
215 has_written = true;
216 }
217 write!(f, "local_data={}", local_data)?;
218 }
219
220 if let Some(pod_uid) = self.pod_uid {
221 if has_written {
222 write!(f, " ")?;
223 } else {
224 has_written = true;
225 }
226 write!(f, "pod_uid={}", pod_uid)?;
227 }
228
229 if let Some(cardinality) = self.cardinality {
230 if has_written {
231 write!(f, " ")?;
232 } else {
233 has_written = true;
234 }
235 write!(f, "cardinality={}", cardinality)?;
236 }
237
238 if let Some(external_data) = self.external_data.as_ref() {
239 if has_written {
240 write!(f, " ")?;
241 }
242 write!(f, "external_data={}", external_data)?;
243 }
244
245 write!(f, ")")
246 }
247}
248
249pub trait OriginTagsResolver: Send + Sync {
251 fn resolve_origin_tags(&self, origin: RawOrigin<'_>) -> SharedTagSet;
253}
254
255impl<T> OriginTagsResolver for Arc<T>
256where
257 T: OriginTagsResolver,
258{
259 fn resolve_origin_tags(&self, origin: RawOrigin<'_>) -> SharedTagSet {
260 (**self).resolve_origin_tags(origin)
261 }
262}
263
264impl<T> OriginTagsResolver for Option<T>
265where
266 T: OriginTagsResolver,
267{
268 fn resolve_origin_tags(&self, origin: RawOrigin<'_>) -> SharedTagSet {
269 self.as_ref()
270 .map(|resolver| resolver.resolve_origin_tags(origin))
271 .unwrap_or_else(|| SharedTagSet::default())
272 }
273}
274
275#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
302pub struct ExternalData {
303 pod_uid: MetaString,
304 container_name: MetaString,
305 init_container: bool,
306}
307
308impl ExternalData {
309 pub fn new(pod_uid: MetaString, container_name: MetaString, init_container: bool) -> Self {
311 Self {
312 pod_uid,
313 container_name,
314 init_container,
315 }
316 }
317
318 pub fn pod_uid(&self) -> &MetaString {
320 &self.pod_uid
321 }
322
323 pub fn container_name(&self) -> &MetaString {
325 &self.container_name
326 }
327
328 pub fn is_init_container(&self) -> bool {
330 self.init_container
331 }
332}
333
334impl std::hash::Hash for ExternalData {
335 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
336 (*self.pod_uid).hash(state);
337 (*self.container_name).hash(state);
338 self.init_container.hash(state);
339 }
340}
341
342#[derive(Clone, Debug, Eq, PartialEq)]
347pub struct RawExternalData<'a> {
348 pod_uid: &'a str,
349 container_name: &'a str,
350 init_container: bool,
351}
352
353impl<'a> RawExternalData<'a> {
354 pub fn try_from_str(raw: &'a str) -> Option<Self> {
358 if raw.is_empty() {
359 return None;
360 }
361
362 let mut data = Self {
363 pod_uid: "",
364 container_name: "",
365 init_container: false,
366 };
367
368 let parts = raw.split(',');
369 for part in parts {
370 if part.len() < 4 {
371 warn!("Parsed external data with invalid key/value pair: {}", part);
374 continue;
375 }
376
377 let key = &part[0..3];
378 let value = &part[3..];
379
380 match key {
381 "it-" => data.init_container = value.parse().unwrap_or(false),
382 "pu-" => data.pod_uid = value,
383 "cn-" => data.container_name = value,
384 _ => {
385 warn!("Parsed external data with unknown key: {}", key);
387 }
388 }
389 }
390
391 Some(data)
392 }
393}
394
395impl std::hash::Hash for RawExternalData<'_> {
396 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
397 self.pod_uid.hash(state);
398 self.container_name.hash(state);
399 self.init_container.hash(state);
400 }
401}
402
403impl fmt::Display for RawExternalData<'_> {
404 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
405 write!(
406 f,
407 "pu-{},cn-{},it-{}",
408 self.pod_uid, self.container_name, self.init_container
409 )
410 }
411}
412
413impl Equivalent<ExternalData> for RawExternalData<'_> {
414 fn equivalent(&self, other: &ExternalData) -> bool {
415 self.pod_uid == &*other.pod_uid
416 && self.container_name == &*other.container_name
417 && self.init_container == other.init_container
418 }
419}
420
421#[cfg(test)]
422mod tests {
423 use std::{
424 collections::hash_map::DefaultHasher,
425 hash::{Hash as _, Hasher as _},
426 };
427
428 use proptest::prelude::*;
429
430 use super::*;
431
432 proptest! {
433 #[test]
434 fn property_test_identical_hash_impls(pod_uid in "[a-z0-9]{1,64}", container_name in "[a-z0-9]{1,64}", init_container in any::<bool>()) {
435 let external_data = ExternalData::new(pod_uid.clone().into(), container_name.clone().into(), init_container);
436 let external_data_ref = RawExternalData {
437 pod_uid: &pod_uid,
438 container_name: &container_name,
439 init_container,
440 };
441
442 let mut hasher = DefaultHasher::new();
443 external_data.hash(&mut hasher);
444 let external_data_hash = hasher.finish();
445
446 let mut hasher = DefaultHasher::new();
447 external_data_ref.hash(&mut hasher);
448 let external_data_ref_hash = hasher.finish();
449
450 assert_eq!(external_data_hash, external_data_ref_hash);
451 }
452 }
453}