parlov-analysis 0.6.0

Analysis engine trait and signal detection for parlov.
Documentation
//! Signal extractors: pure functions that observe differentials and produce typed signals.
//!
//! Each extractor takes a `&DifferentialSet` and returns `Vec<Signal>`. Extractors are
//! independent and composable — the analyzer runs all extractors unconditionally on every
//! `DifferentialSet`.

pub mod body;
pub mod header;
pub mod metadata;
pub mod status_code;

#[cfg(test)]
pub(crate) mod tests {
    use bytes::Bytes;
    use http::{HeaderMap, StatusCode};
    use parlov_core::{
        DifferentialSet, NormativeStrength, OracleClass, ProbeDefinition, ProbeExchange,
        ResponseSurface, Technique, Vector,
    };

    /// Builds a `ProbeExchange` with the given status and empty headers/body.
    pub fn fake_exchange(status: u16) -> ProbeExchange {
        ProbeExchange {
            request: ProbeDefinition {
                url: "http://test/resource/1".to_string(),
                method: http::Method::GET,
                headers: HeaderMap::new(),
                body: None,
            },
            response: ResponseSurface {
                status: StatusCode::from_u16(status).expect("valid status"),
                headers: HeaderMap::new(),
                body: Bytes::new(),
                timing_ns: 0,
            },
        }
    }

    /// Builds a `ProbeExchange` with custom headers on the response.
    pub fn fake_exchange_with_headers(status: u16, headers: HeaderMap) -> ProbeExchange {
        ProbeExchange {
            request: ProbeDefinition {
                url: "http://test/resource/1".to_string(),
                method: http::Method::GET,
                headers: HeaderMap::new(),
                body: None,
            },
            response: ResponseSurface {
                status: StatusCode::from_u16(status).expect("valid status"),
                headers,
                body: Bytes::new(),
                timing_ns: 0,
            },
        }
    }

    /// Default `Technique` for status-code-diff tests.
    pub fn status_code_diff_technique() -> Technique {
        Technique {
            id: "test-status-diff",
            name: "Test status code diff",
            oracle_class: OracleClass::Existence,
            vector: Vector::StatusCodeDiff,
            strength: NormativeStrength::Must,
        }
    }

    /// Builds a single-sample `DifferentialSet` from baseline and probe status codes.
    pub fn single_diff_set(baseline_status: u16, probe_status: u16) -> DifferentialSet {
        DifferentialSet {
            baseline: vec![fake_exchange(baseline_status)],
            probe: vec![fake_exchange(probe_status)],
            technique: status_code_diff_technique(),
        }
    }

    /// Builds a multi-sample `DifferentialSet` from status code slices.
    pub fn diff_set_with_statuses(baseline: &[u16], probe: &[u16]) -> DifferentialSet {
        DifferentialSet {
            baseline: baseline.iter().map(|&s| fake_exchange(s)).collect(),
            probe: probe.iter().map(|&s| fake_exchange(s)).collect(),
            technique: status_code_diff_technique(),
        }
    }

    /// Builds a single-sample `DifferentialSet` with custom headers on both sides.
    pub fn single_diff_set_with_headers(
        baseline_status: u16,
        probe_status: u16,
        baseline_headers: HeaderMap,
        probe_headers: HeaderMap,
    ) -> DifferentialSet {
        DifferentialSet {
            baseline: vec![fake_exchange_with_headers(baseline_status, baseline_headers)],
            probe: vec![fake_exchange_with_headers(probe_status, probe_headers)],
            technique: status_code_diff_technique(),
        }
    }

    /// Builds a single-sample `DifferentialSet` with custom headers on baseline only.
    pub fn single_diff_set_with_baseline_headers(
        baseline_status: u16,
        probe_status: u16,
        baseline_headers: HeaderMap,
    ) -> DifferentialSet {
        DifferentialSet {
            baseline: vec![fake_exchange_with_headers(baseline_status, baseline_headers)],
            probe: vec![fake_exchange(probe_status)],
            technique: status_code_diff_technique(),
        }
    }

    /// Builds a `ProbeExchange` with custom headers and body.
    pub fn fake_exchange_with_body(status: u16, body: &[u8]) -> ProbeExchange {
        ProbeExchange {
            request: ProbeDefinition {
                url: "http://test/resource/1".to_string(),
                method: http::Method::GET,
                headers: HeaderMap::new(),
                body: None,
            },
            response: ResponseSurface {
                status: StatusCode::from_u16(status).expect("valid status"),
                headers: HeaderMap::new(),
                body: Bytes::from(body.to_vec()),
                timing_ns: 0,
            },
        }
    }

    /// Builds a `DifferentialSet` with custom bodies on both sides.
    pub fn single_diff_set_with_bodies(
        baseline_status: u16,
        probe_status: u16,
        baseline_body: &[u8],
        probe_body: &[u8],
    ) -> DifferentialSet {
        DifferentialSet {
            baseline: vec![fake_exchange_with_body(baseline_status, baseline_body)],
            probe: vec![fake_exchange_with_body(probe_status, probe_body)],
            technique: status_code_diff_technique(),
        }
    }
}