Skip to main content

dag_ml_data_core/
ids.rs

1use std::fmt;
2
3use serde::{Deserialize, Serialize};
4
5use crate::error::{DataError, Result};
6
7fn validate_identifier(value: &str) -> Result<()> {
8    if value.is_empty() {
9        return Err(DataError::InvalidIdentifier {
10            value: value.to_string(),
11            reason: "identifier is empty",
12        });
13    }
14    if value.len() > 128 {
15        return Err(DataError::InvalidIdentifier {
16            value: value.to_string(),
17            reason: "identifier is longer than 128 bytes",
18        });
19    }
20    if !value
21        .bytes()
22        .all(|b| b.is_ascii_alphanumeric() || matches!(b, b'_' | b'-' | b'.'))
23    {
24        return Err(DataError::InvalidIdentifier {
25            value: value.to_string(),
26            reason: "identifier contains unsupported characters",
27        });
28    }
29    Ok(())
30}
31
32macro_rules! define_id {
33    ($name:ident) => {
34        #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
35        #[serde(try_from = "String", into = "String")]
36        pub struct $name(String);
37
38        impl $name {
39            pub fn new(value: impl Into<String>) -> Result<Self> {
40                let value = value.into();
41                validate_identifier(&value)?;
42                Ok(Self(value))
43            }
44
45            pub fn as_str(&self) -> &str {
46                &self.0
47            }
48        }
49
50        impl TryFrom<String> for $name {
51            type Error = DataError;
52
53            fn try_from(value: String) -> Result<Self> {
54                Self::new(value)
55            }
56        }
57
58        impl From<$name> for String {
59            fn from(value: $name) -> Self {
60                value.0
61            }
62        }
63
64        impl fmt::Display for $name {
65            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66                self.0.fmt(f)
67            }
68        }
69    };
70}
71
72define_id!(SampleId);
73define_id!(SourceId);
74define_id!(RepresentationId);
75define_id!(TypeId);
76define_id!(ObservationId);
77define_id!(TargetId);
78define_id!(GroupId);
79define_id!(OriginId);
80define_id!(RepetitionId);
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85
86    #[test]
87    fn accepts_data_ids() {
88        assert!(SourceId::new("nir.source_1").is_ok());
89    }
90
91    #[test]
92    fn rejects_graph_style_colon_for_data_ids() {
93        assert!(SourceId::new("source:nir").is_err());
94    }
95}