Skip to main content

coil_observability/
validation.rs

1use 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}