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