safe_http 0.1.0-beta.4

Simple and safe HTTP types.
Documentation
mod constants;
mod generated_struct;
mod invalid;

pub use self::{generated_struct::HeaderName, invalid::InvalidHeaderName};

use shared_bytes::SharedStr;

const fn validate_static(bytes: &[u8]) -> Result<(), InvalidHeaderName> {
    validate(bytes)
}

fn validate_with_normalized_percent_encoding(
    string: &str,
) -> Result<Option<SharedStr>, InvalidHeaderName> {
    validate(string.as_bytes()).map(|()| None)
}

const fn validate(bytes: &[u8]) -> Result<(), InvalidHeaderName> {
    match crate::rfc::token::validate(bytes) {
        Ok(()) => Ok(()),
        Err(e) => Err(InvalidHeaderName { byte: e.byte }),
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use assert_matches::assert_matches;
    use std::{
        collections::hash_map::DefaultHasher,
        hash::{Hash, Hasher},
    };

    #[test]
    fn equals_ignores_case() {
        let upper_case = HeaderName::from_static("Content-Type");
        let lower_case = HeaderName::try_from(upper_case.as_str().to_ascii_lowercase()).unwrap();
        assert_eq!(lower_case, upper_case);
    }

    #[test]
    fn hash_ignores_case() {
        let upper_case = HeaderName::from_static("Content-Type");
        let lower_case = HeaderName::try_from(upper_case.as_str().to_ascii_lowercase()).unwrap();
        assert_eq!(hash(&lower_case), hash(&upper_case));
    }

    fn hash<T: Hash>(t: &T) -> u64 {
        let mut hasher = DefaultHasher::new();
        t.hash(&mut hasher);
        hasher.finish()
    }

    #[test]
    fn generated_constants_are_valid() {
        for constant in HeaderName::GENERATED_CONSTANTS
            .iter()
            .map(|c| c.clone().into_shared_str())
        {
            // Invalid constants are a compile error if those constants are used.
            // So these asserts are just there to double check.
            assert_matches!(HeaderName::try_from(constant.clone()), Ok(name) => assert_eq!(constant, name.as_str()));
        }
    }
}