coil_observability/
validation.rs1use crate::ObservabilityError;
2use std::fmt;
3
4macro_rules! token_type {
5 ($name:ident, $field:literal) => {
6 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
7 pub struct $name(String);
8
9 impl $name {
10 pub fn new(value: impl Into<String>) -> Result<Self, ObservabilityError> {
11 Ok(Self(validate_token($field, value.into())?))
12 }
13
14 pub fn as_str(&self) -> &str {
15 &self.0
16 }
17 }
18
19 impl fmt::Display for $name {
20 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21 f.write_str(&self.0)
22 }
23 }
24 };
25}
26
27token_type!(MetricName, "metric_name");
28token_type!(DimensionKey, "dimension_key");
29token_type!(FeatureFlagId, "feature_flag_id");
30token_type!(CustomerAppId, "customer_app_id");
31token_type!(SiteId, "site_id");
32token_type!(BrandId, "brand_id");
33token_type!(CohortId, "cohort_id");
34
35pub fn validate_token(field: &'static str, value: String) -> Result<String, ObservabilityError> {
36 let trimmed = value.trim();
37 if trimmed.is_empty() {
38 return Err(ObservabilityError::EmptyField { field });
39 }
40
41 if trimmed
42 .chars()
43 .all(|ch| ch.is_ascii_alphanumeric() || matches!(ch, '-' | '_' | '.' | ':'))
44 {
45 Ok(trimmed.to_string())
46 } else {
47 Err(ObservabilityError::InvalidToken {
48 field,
49 value: trimmed.to_string(),
50 })
51 }
52}