use candid::{CandidType, types};
use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
use std::{fmt, str::FromStr};
use thiserror::Error;
#[derive(Clone, Debug, Eq, Error, PartialEq)]
pub enum IdentifierError {
#[error("identifier must contain exactly 64 lowercase hexadecimal characters")]
NonCanonicalId,
#[error("role must be nonempty and contain only a-z, 0-9, underscore, colon or hyphen")]
InvalidRole,
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct CanonicalId([u8; 32]);
impl CanonicalId {
pub const fn from_bytes(bytes: [u8; 32]) -> Self {
Self(bytes)
}
pub const fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
}
impl fmt::Display for CanonicalId {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
for byte in self.0 {
write!(formatter, "{byte:02x}")?;
}
Ok(())
}
}
impl FromStr for CanonicalId {
type Err = IdentifierError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
if value.len() != 64
|| !value
.bytes()
.all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b))
{
return Err(IdentifierError::NonCanonicalId);
}
let mut bytes = [0; 32];
for (byte, pair) in bytes.iter_mut().zip(value.as_bytes().as_chunks::<2>().0) {
let nibble = |b| if b <= b'9' { b - b'0' } else { b - b'a' + 10 };
*byte = (nibble(pair[0]) << 4) | nibble(pair[1]);
}
Ok(Self(bytes))
}
}
impl Serialize for CanonicalId {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.collect_str(self)
}
}
impl<'de> Deserialize<'de> for CanonicalId {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
String::deserialize(deserializer)?
.parse()
.map_err(de::Error::custom)
}
}
impl CandidType for CanonicalId {
fn _ty() -> types::Type {
types::TypeInner::Text.into()
}
fn idl_serialize<S: types::Serializer>(&self, serializer: S) -> Result<(), S::Error> {
serializer.serialize_text(&self.to_string())
}
}
#[derive(
CandidType, Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize,
)]
#[serde(deny_unknown_fields)]
pub struct AudienceId {
pub canonical_network_id: CanonicalId,
pub fleet_id: CanonicalId,
}
#[derive(CandidType, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
#[serde(transparent)]
pub struct AuthRole(String);
impl AuthRole {
pub fn as_str(&self) -> &str {
&self.0
}
}
impl FromStr for AuthRole {
type Err = IdentifierError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
if value.is_empty()
|| !value.bytes().all(|b| {
b.is_ascii_lowercase() || b.is_ascii_digit() || matches!(b, b'_' | b':' | b'-')
})
{
return Err(IdentifierError::InvalidRole);
}
Ok(Self(value.to_owned()))
}
}
impl<'de> Deserialize<'de> for AuthRole {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
String::deserialize(deserializer)?
.parse()
.map_err(de::Error::custom)
}
}