Skip to main content

arete_hash/
identifier.rs

1use std::fmt;
2use std::str::FromStr;
3
4use serde::{Deserialize, Deserializer, Serialize};
5
6use crate::HashError;
7
8macro_rules! prefixed_identifier {
9    ($type:ident, $projection:literal, $prefix:literal) => {
10        #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
11        #[serde(transparent)]
12        pub struct $type(String);
13
14        impl $type {
15            pub fn new(value: impl Into<String>) -> Result<Self, HashError> {
16                let value = value.into();
17                let suffix =
18                    value
19                        .strip_prefix($prefix)
20                        .ok_or_else(|| HashError::InvalidProjection {
21                            projection: $projection,
22                            reason: concat!("identifier must begin with '", $prefix, "'")
23                                .to_string(),
24                        })?;
25                if suffix.len() != 32
26                    || !suffix
27                        .bytes()
28                        .all(|byte| byte.is_ascii_alphanumeric() || byte == b'_' || byte == b'-')
29                {
30                    return Err(HashError::InvalidProjection {
31                        projection: $projection,
32                        reason: "identifier suffix must contain exactly 32 URL-safe characters"
33                            .to_string(),
34                    });
35                }
36                Ok(Self(value))
37            }
38
39            pub fn as_str(&self) -> &str {
40                &self.0
41            }
42        }
43
44        impl fmt::Display for $type {
45            fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
46                formatter.write_str(&self.0)
47            }
48        }
49
50        impl FromStr for $type {
51            type Err = HashError;
52
53            fn from_str(value: &str) -> Result<Self, Self::Err> {
54                Self::new(value)
55            }
56        }
57
58        impl<'de> Deserialize<'de> for $type {
59            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
60            where
61                D: Deserializer<'de>,
62            {
63                String::deserialize(deserializer)?
64                    .parse()
65                    .map_err(serde::de::Error::custom)
66            }
67        }
68    };
69}
70
71prefixed_identifier!(ProgramReadBindingId, "program read binding", "prb_");
72prefixed_identifier!(DecoderBindingId, "decoder binding", "dec_");
73
74#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
75#[serde(transparent)]
76pub struct DecoderEngineId(String);
77
78impl DecoderEngineId {
79    pub fn new(value: impl Into<String>) -> Result<Self, HashError> {
80        let value = value.into();
81        if value.is_empty() || value.len() > 128 {
82            return Err(HashError::InvalidProjection {
83                projection: "decoder engine",
84                reason: "identifier must contain between 1 and 128 bytes".to_string(),
85            });
86        }
87        Ok(Self(value))
88    }
89
90    pub fn as_str(&self) -> &str {
91        &self.0
92    }
93}
94
95impl fmt::Display for DecoderEngineId {
96    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
97        formatter.write_str(&self.0)
98    }
99}
100
101impl FromStr for DecoderEngineId {
102    type Err = HashError;
103
104    fn from_str(value: &str) -> Result<Self, Self::Err> {
105        Self::new(value)
106    }
107}
108
109impl<'de> Deserialize<'de> for DecoderEngineId {
110    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
111    where
112        D: Deserializer<'de>,
113    {
114        String::deserialize(deserializer)?
115            .parse()
116            .map_err(serde::de::Error::custom)
117    }
118}