spiffe-rs 0.1.0

Rust port of spiffe-go with SPIFFE IDs, bundles, SVIDs, Workload API client, federation helpers, and rustls-based SPIFFE TLS utilities.
Documentation
use crate::spiffeid::charset::is_backcompat_trust_domain_char;
use crate::spiffeid::id::make_id;
use crate::spiffeid::{Error, ID, Result};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::cmp::Ordering;
use url::Url;

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct TrustDomain {
    pub(crate) name: String,
}

pub fn trust_domain_from_string(id_or_name: &str) -> Result<TrustDomain> {
    if id_or_name.is_empty() {
        return Err(Error::MissingTrustDomain);
    }
    if id_or_name.contains(":/") {
        let id = ID::from_string(id_or_name)?;
        return Ok(id.trust_domain());
    }
    for &c in id_or_name.as_bytes() {
        if !is_valid_trust_domain_char(c) {
            return Err(Error::BadTrustDomainChar);
        }
    }
    Ok(TrustDomain {
        name: id_or_name.to_string(),
    })
}

pub fn trust_domain_from_uri(uri: &Url) -> Result<TrustDomain> {
    let id = ID::from_uri(uri)?;
    Ok(id.trust_domain())
}

impl TrustDomain {
    pub fn name(&self) -> &str {
        &self.name
    }

    pub fn id(&self) -> ID {
        make_id(self, "").unwrap_or_else(|_| ID::zero())
    }

    pub fn id_string(&self) -> String {
        self.id().to_string()
    }

    pub fn is_zero(&self) -> bool {
        self.name.is_empty()
    }

    pub fn compare(&self, other: &TrustDomain) -> Ordering {
        self.name.cmp(&other.name)
    }

    pub fn marshal_text(&self) -> Option<Vec<u8>> {
        if self.is_zero() {
            None
        } else {
            Some(self.name.as_bytes().to_vec())
        }
    }

    pub fn unmarshal_text(&mut self, text: &[u8]) -> Result<()> {
        if text.is_empty() {
            *self = TrustDomain { name: String::new() };
            return Ok(());
        }
        let parsed = trust_domain_from_string(std::str::from_utf8(text).map_err(|e| {
            Error::Other(format!("invalid trust domain text: {}", e))
        })?)?;
        *self = parsed;
        Ok(())
    }
}

impl std::fmt::Display for TrustDomain {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.name.fmt(f)
    }
}

impl Default for TrustDomain {
    fn default() -> Self {
        TrustDomain { name: String::new() }
    }
}

impl Serialize for TrustDomain {
    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        if self.is_zero() {
            serializer.serialize_str("")
        } else {
            serializer.serialize_str(&self.name)
        }
    }
}

impl<'de> Deserialize<'de> for TrustDomain {
    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        if s.is_empty() {
            Ok(TrustDomain { name: String::new() })
        } else {
            trust_domain_from_string(&s).map_err(serde::de::Error::custom)
        }
    }
}

fn is_valid_trust_domain_char(c: u8) -> bool {
    matches!(c, b'a'..=b'z')
        || matches!(c, b'0'..=b'9')
        || matches!(c, b'-' | b'.' | b'_')
        || is_backcompat_trust_domain_char(c)
}