cc-lb-plugin-conformance 0.1.3

cc-lb plugin conformance suite — in-process protocol verification helpers for external plugin authors.
Documentation
use std::collections::BTreeSet;

use cc_lb_plugin_wire::v2::common::CandidateWire;
use cc_lb_plugin_wire::v3::filter::{FilterFn, FilterRequest};
use cc_lb_plugin_wire::wire_function::WireFunction;

use crate::dispatch::PluginSession;
use crate::errors::DispatchError;
use crate::fixtures;
use crate::handshake;
use crate::{VerifyError, VerifyReport};

use super::{begin_report, dispatch_ok, push_dispatch_ok, require_function};

pub fn verify_router_plugin(wasm: &[u8]) -> Result<VerifyReport, VerifyError> {
    verify_router_plugin_with_caps(wasm, &BTreeSet::new())
}

pub fn verify_router_plugin_with_caps(
    wasm: &[u8],
    host_capabilities: &BTreeSet<String>,
) -> Result<VerifyReport, VerifyError> {
    let handshake = handshake::run_with_caps(wasm, host_capabilities)?;
    require_function::<FilterFn>(&handshake)?;

    let mut report = begin_report(wasm, handshake, FilterFn::NAME)?;
    let mut session = PluginSession::new_with_caps(wasm, host_capabilities)?;

    let empty_response = dispatch_ok::<FilterFn>(&mut session, FilterRequest::dry_run_sample())?;
    validate_decision_count(0, empty_response.results.len())?;
    push_dispatch_ok(
        &mut report,
        "dispatch:filter:empty_candidates",
        Some("0 candidates -> 0 decisions".to_owned()),
    );

    let candidates = vec![CandidateWire::dry_run_sample()];
    let request = fixtures::filter_request_builder()
        .candidates(candidates.clone())
        .build();
    let response = dispatch_ok::<FilterFn>(&mut session, request)?;
    validate_decision_count(candidates.len(), response.results.len())?;
    push_dispatch_ok(
        &mut report,
        "dispatch:filter:candidate_count",
        Some(format!(
            "{} candidate(s) -> matching decisions",
            candidates.len()
        )),
    );

    Ok(report)
}

fn validate_decision_count(expected: usize, actual: usize) -> Result<(), VerifyError> {
    if expected == actual {
        Ok(())
    } else {
        Err(DispatchError::InvalidResponse {
            function: "filter",
            reason: format!("expected {expected} decision(s), got {actual}"),
        }
        .into())
    }
}