parlov-analysis 0.3.0

Analysis engine trait and signal detection for parlov.
Documentation
//! Header diffing for the existence oracle.

use std::collections::BTreeSet;

use http::HeaderMap;
use parlov_core::DiffedHeader;

/// Compares all headers between two sides; returns only those that differ.
///
/// A header absent on one side has `None` for that side's value. Multi-value
/// headers use the first value only. Header names are normalised to lowercase.
pub(super) fn diff_headers(baseline: &HeaderMap, probe: &HeaderMap) -> Vec<DiffedHeader> {
    let names: BTreeSet<&str> = baseline
        .keys()
        .chain(probe.keys())
        .map(http::HeaderName::as_str)
        .collect();

    names
        .into_iter()
        .filter_map(|name| {
            let b_val = header_str(baseline, name);
            let p_val = header_str(probe, name);
            if b_val == p_val {
                return None;
            }
            Some(DiffedHeader {
                name: name.to_owned(),
                baseline: b_val,
                probe: p_val,
            })
        })
        .collect()
}

fn header_str(map: &HeaderMap, name: &str) -> Option<String> {
    map.get(name)
        .and_then(|v| v.to_str().ok())
        .map(str::to_owned)
}

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

    fn map_with(name: &str, value: &str) -> HeaderMap {
        let mut m = HeaderMap::new();
        m.insert(
            HeaderName::from_bytes(name.as_bytes()).unwrap(),
            HeaderValue::from_str(value).unwrap(),
        );
        m
    }

    #[test]
    fn identical_headers_produce_no_diffs() {
        let a = map_with("x-foo", "bar");
        let b = map_with("x-foo", "bar");
        assert!(diff_headers(&a, &b).is_empty());
    }

    #[test]
    fn differing_value_produces_one_entry() {
        let a = map_with("x-foo", "one");
        let b = map_with("x-foo", "two");
        let diffs = diff_headers(&a, &b);
        assert_eq!(diffs.len(), 1);
        assert_eq!(diffs[0].name, "x-foo");
        assert_eq!(diffs[0].baseline, Some("one".to_owned()));
        assert_eq!(diffs[0].probe, Some("two".to_owned()));
    }

    #[test]
    fn header_absent_on_probe_side() {
        let a = map_with("x-only-baseline", "hello");
        let b = HeaderMap::new();
        let diffs = diff_headers(&a, &b);
        assert_eq!(diffs.len(), 1);
        assert_eq!(diffs[0].baseline, Some("hello".to_owned()));
        assert_eq!(diffs[0].probe, None);
    }

    #[test]
    fn header_absent_on_baseline_side() {
        let a = HeaderMap::new();
        let b = map_with("x-only-probe", "world");
        let diffs = diff_headers(&a, &b);
        assert_eq!(diffs.len(), 1);
        assert_eq!(diffs[0].baseline, None);
        assert_eq!(diffs[0].probe, Some("world".to_owned()));
    }

    #[test]
    fn both_empty_maps_produce_no_diffs() {
        assert!(diff_headers(&HeaderMap::new(), &HeaderMap::new()).is_empty());
    }
}