rama-http-headers 0.3.0

typed http headers for rama
Documentation
use base64::{Engine, engine::general_purpose::STANDARD};
use rama_http_types::HeaderValue;

use crate::{HeaderDecode, HeaderEncode, TypedHeader};

/// The `Sec-WebSocket-Key` header.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct SecWebSocketKey(pub(super) HeaderValue);

impl SecWebSocketKey {
    #[must_use]
    pub fn random() -> Self {
        let r: [u8; 16] = rand::random();
        r.into()
    }
}

impl TypedHeader for SecWebSocketKey {
    fn name() -> &'static ::rama_http_types::header::HeaderName {
        &::rama_http_types::header::SEC_WEBSOCKET_KEY
    }
}

impl HeaderDecode for SecWebSocketKey {
    fn decode<'i, I>(values: &mut I) -> Result<Self, crate::Error>
    where
        I: Iterator<Item = &'i ::rama_http_types::header::HeaderValue>,
    {
        let value = crate::util::TryFromValues::try_from_values(values).map(SecWebSocketKey)?;
        let mut k = [0u8; 16];
        if STANDARD.decode_slice(value.0.as_bytes(), &mut k[..]).ok() != Some(16) {
            Err(crate::Error::invalid())
        } else {
            Ok(value)
        }
    }
}

impl HeaderEncode for SecWebSocketKey {
    fn encode<E: Extend<::rama_http_types::HeaderValue>>(&self, values: &mut E) {
        values.extend(::std::iter::once((&self.0).into()));
    }
}

impl From<[u8; 16]> for SecWebSocketKey {
    fn from(bytes: [u8; 16]) -> Self {
        #[expect(
            clippy::expect_used,
            reason = " ASSUMPTION standard base64 encoding of 16 bytes is always valid http header"
        )]
        let mut value = HeaderValue::try_from(STANDARD.encode(bytes))
            .expect(" ASSUMPTION standard base64 encoding of 16 bytes is always valid http header");
        value.set_sensitive(true);
        Self(value)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::common::test_decode;

    #[test]
    fn from_bytes() {
        let bytes: [u8; 16] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
        _ = SecWebSocketKey::from(bytes);
    }

    #[test]
    fn test_invalid_websocket_key_empty() {
        assert!(test_decode::<SecWebSocketKey>(&[""]).is_none());
    }

    #[test]
    fn test_invalid_websocket_key_too_long() {
        assert!(test_decode::<SecWebSocketKey>(&["dGhlIHNhbXBsZSBub25jZQ==AAAAAAAAAA"]).is_none());
    }

    #[test]
    fn test_invalid_websocket_key_base64_symbol() {
        assert!(test_decode::<SecWebSocketKey>(&["dGhlIHNhbXBsZSBub25jZQ!!"]).is_none());
    }

    #[test]
    fn test_invalid_websocket_key_decoded_length() {
        assert!(test_decode::<SecWebSocketKey>(&["AAAAAAAAAAAAAAAAAAAAAAAA"]).is_none());
    }

    #[test]
    fn test_valid_websocket_key() {
        _ = test_decode::<SecWebSocketKey>(&["dGhlIHNhbXBsZSBub25jZQ=="]).unwrap();
    }
}