ic_auth_protocol_types/
identifiers.rs1use candid::{CandidType, types};
5use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
6use std::{fmt, str::FromStr};
7use thiserror::Error;
8
9#[derive(Clone, Debug, Eq, Error, PartialEq)]
11pub enum IdentifierError {
12 #[error("identifier must contain exactly 64 lowercase hexadecimal characters")]
13 NonCanonicalId,
14 #[error("role must be nonempty and contain only a-z, 0-9, underscore, colon or hyphen")]
15 InvalidRole,
16}
17
18#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
22pub struct CanonicalId([u8; 32]);
23
24impl CanonicalId {
25 pub const fn from_bytes(bytes: [u8; 32]) -> Self {
27 Self(bytes)
28 }
29
30 pub const fn as_bytes(&self) -> &[u8; 32] {
32 &self.0
33 }
34}
35
36impl fmt::Display for CanonicalId {
37 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
38 for byte in self.0 {
39 write!(formatter, "{byte:02x}")?;
40 }
41 Ok(())
42 }
43}
44
45impl FromStr for CanonicalId {
46 type Err = IdentifierError;
47
48 fn from_str(value: &str) -> Result<Self, Self::Err> {
49 if value.len() != 64
50 || !value
51 .bytes()
52 .all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b))
53 {
54 return Err(IdentifierError::NonCanonicalId);
55 }
56 let mut bytes = [0; 32];
57 for (byte, pair) in bytes.iter_mut().zip(value.as_bytes().as_chunks::<2>().0) {
58 let nibble = |b| if b <= b'9' { b - b'0' } else { b - b'a' + 10 };
59 *byte = (nibble(pair[0]) << 4) | nibble(pair[1]);
60 }
61 Ok(Self(bytes))
62 }
63}
64
65impl Serialize for CanonicalId {
66 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
67 serializer.collect_str(self)
68 }
69}
70
71impl<'de> Deserialize<'de> for CanonicalId {
72 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
73 String::deserialize(deserializer)?
74 .parse()
75 .map_err(de::Error::custom)
76 }
77}
78
79impl CandidType for CanonicalId {
80 fn _ty() -> types::Type {
81 types::TypeInner::Text.into()
82 }
83
84 fn idl_serialize<S: types::Serializer>(&self, serializer: S) -> Result<(), S::Error> {
85 serializer.serialize_text(&self.to_string())
86 }
87}
88
89#[derive(
93 CandidType, Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize,
94)]
95#[serde(deny_unknown_fields)]
96pub struct AudienceId {
97 pub canonical_network_id: CanonicalId,
98 pub fleet_id: CanonicalId,
99}
100
101#[derive(CandidType, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
105#[serde(transparent)]
106pub struct AuthRole(String);
107
108impl AuthRole {
109 pub fn as_str(&self) -> &str {
110 &self.0
111 }
112}
113
114impl FromStr for AuthRole {
115 type Err = IdentifierError;
116
117 fn from_str(value: &str) -> Result<Self, Self::Err> {
118 if value.is_empty()
119 || !value.bytes().all(|b| {
120 b.is_ascii_lowercase() || b.is_ascii_digit() || matches!(b, b'_' | b':' | b'-')
121 })
122 {
123 return Err(IdentifierError::InvalidRole);
124 }
125 Ok(Self(value.to_owned()))
126 }
127}
128
129impl<'de> Deserialize<'de> for AuthRole {
130 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
131 String::deserialize(deserializer)?
132 .parse()
133 .map_err(de::Error::custom)
134 }
135}