gov-uk-sdk-core 0.1.0

Shared HTTP client, auth, errors, and content negotiation for GOV.UK / Companies House SDK crates.
Documentation
use http::HeaderMap;

/// Deprecation metadata from `CH-Expiry-Date` (Companies House).
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ChDeprecation {
    pub raw: String,
    pub expiry_date: Option<String>,
    /// Deprecated MIME / profile, may include `; version=N` when present in the header.
    pub rel: Option<String>,
}

/// Parses the Companies House `CH-Expiry-Date` header when present.
pub(crate) fn parse_ch_expiry(headers: &HeaderMap) -> Option<ChDeprecation> {
    let raw = headers
        .get("CH-Expiry-Date")?
        .to_str()
        .ok()?
        .to_string();

    let (expiry_date, rel) = parse_ch_expiry_parts(&raw);

    Some(ChDeprecation {
        raw,
        expiry_date,
        rel,
    })
}

/// Splits the raw header value into expiry date and deprecated MIME / `rel` fragment.
fn parse_ch_expiry_parts(raw: &str) -> (Option<String>, Option<String>) {
    let rel_key = "; rel=";
    if let Some(pos) = raw.find(rel_key) {
        let date_part = raw[..pos].trim();
        let exp: Option<String> = if date_part.is_empty() {
            None
        } else {
            Some(date_part.to_string())
        };
        let r = raw[pos + rel_key.len()..].trim();
        let rel = if r.is_empty() {
            None
        } else {
            Some(r.to_string())
        };
        return (exp, rel);
    }

    let mut expiry_date: Option<String> = None;
    let mut rel: Option<String> = None;
    for part in raw.split(';').map(str::trim) {
        if let Some(r) = part.strip_prefix("rel=") {
            rel = Some(r.trim().to_string());
        } else if !part.is_empty() && !part.contains('=') && expiry_date.is_none() {
            expiry_date = Some(part.to_string());
        }
    }
    (expiry_date, rel)
}

#[cfg(test)]
mod tests {
    use super::*;
    use http::HeaderValue;

    /// Covers `rel=` with a value that itself contains `; version=`.
    #[test]
    fn parses_ch_expiry_header_with_rel_and_trailing_version() {
        let mut h = HeaderMap::new();
        h.insert(
            "CH-Expiry-Date",
            HeaderValue::from_static(
                "2016-01-01; rel=application/vnd.companieshouse.company-profile+json; version=1",
            ),
        );
        let d = parse_ch_expiry(&h).expect("parsed");
        assert_eq!(d.expiry_date.as_deref(), Some("2016-01-01"));
        assert_eq!(
            d.rel.as_deref(),
            Some("application/vnd.companieshouse.company-profile+json; version=1")
        );
    }
}