spiral-contract 0.1.12

Language-neutral service wire values
Documentation
//! Authorization values passed to service implementations for verification.

use std::fmt::{self, Debug, Formatter};

/// An owned Authorization header value without parsing or authentication.
///
/// Service implementations interpret the authorization scheme and verify the
/// credential. Debug output redacts the value; use [`Self::as_str`] explicitly.
///
/// The value is for the service's API unless the entry point that received it
/// names another protected resource (RFC 8707), such as an MCP endpoint. The
/// entry point sets that resource, never the caller, and verification requires
/// a token issued for it.
#[derive(Clone, Eq, PartialEq)]
pub struct AuthHeader {
    value: String,
    resource: Option<String>,
}

impl AuthHeader {
    /// Preserve the complete Authorization header value without interpreting it.
    pub fn new(value: impl Into<String>) -> Self {
        Self {
            value: value.into(),
            resource: None,
        }
    }

    /// The same value, presented to `resource` rather than to the service's API.
    pub fn for_resource(self, resource: impl Into<String>) -> Self {
        Self {
            resource: Some(resource.into()),
            ..self
        }
    }

    /// Expose the complete value for verification by the service implementation.
    pub fn as_str(&self) -> &str {
        &self.value
    }

    /// The protected resource the value was presented to, when it is not the service's API.
    pub fn resource(&self) -> Option<&str> {
        self.resource.as_deref()
    }
}

impl From<String> for AuthHeader {
    fn from(value: String) -> Self {
        Self::new(value)
    }
}

impl From<&str> for AuthHeader {
    fn from(value: &str) -> Self {
        Self::new(value)
    }
}

impl Debug for AuthHeader {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
        formatter.write_str("AuthHeader([REDACTED])")
    }
}

#[cfg(test)]
mod tests {
    use super::AuthHeader;

    #[test]
    fn preserves_authorization_values_and_redacts_debug_output() {
        let value = "CustomScheme  secret.token ";
        let header = AuthHeader::new(value);
        assert_eq!(header.as_str(), value);
        assert_eq!(header.clone(), header);
        assert_eq!(format!("{header:?}"), "AuthHeader([REDACTED])");
        assert_eq!(AuthHeader::new("").as_str(), "");
    }
}