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}