weavatrix-rust 2.1.1

Protocol-independent Rust repository intelligence: typed evidence graphs for impact, architecture, APIs, Git, search, and memory
Documentation
use super::{
    BTreeMap, BTreeSet, Certainty, ContractKey, Entity, Observation, Role, Transport, Value, json,
};

#[derive(Default)]
pub(super) struct Evaluation {
    pub(super) contracts: Vec<Value>,
    pub(super) matches: Vec<Value>,
    pub(super) mismatches: Vec<Value>,
    pub(super) ambiguities: Vec<Value>,
}

pub(super) fn evaluate(
    observations: &[Observation],
    max_contracts: usize,
    per_contract: usize,
) -> Evaluation {
    let grouped = group_observations(observations);
    let bindings = observations
        .iter()
        .filter(|observation| {
            matches!(observation.transport, Transport::Amqp | Transport::RabbitMq)
        })
        .filter(|observation| observation.role == Role::Bind)
        .collect::<Vec<_>>();
    let mut evaluation = Evaluation::default();
    for (key, evidence) in grouped.into_iter().take(max_contracts) {
        evaluate_contract(
            &key,
            &evidence,
            &bindings,
            observations,
            per_contract,
            &mut evaluation,
        );
    }
    add_cross_kind_mismatches(observations, &mut evaluation.mismatches);
    evaluation
}

fn group_observations(observations: &[Observation]) -> BTreeMap<ContractKey, Vec<&Observation>> {
    let mut grouped: BTreeMap<ContractKey, Vec<&Observation>> = BTreeMap::new();
    for observation in observations {
        if observation.certainty != Certainty::Ambiguous
            && let Some(key) = observation.key()
        {
            grouped.entry(key).or_default().push(observation);
        }
    }
    grouped
}

fn evaluate_contract<'a>(
    key: &ContractKey,
    evidence: &[&'a Observation],
    bindings: &[&'a Observation],
    observations: &'a [Observation],
    per_contract: usize,
    evaluation: &mut Evaluation,
) {
    let producers = evidence
        .iter()
        .copied()
        .filter(|item| item.role == Role::Producer)
        .collect::<Vec<_>>();
    let mut consumers = evidence
        .iter()
        .copied()
        .filter(|item| item.role == Role::Consumer)
        .collect::<Vec<_>>();
    let declarations = evidence
        .iter()
        .copied()
        .filter(|item| matches!(item.role, Role::Declare | Role::Bind))
        .collect::<Vec<_>>();
    let routed = route_amqp_consumers(
        key,
        &producers,
        bindings,
        observations,
        &mut consumers,
        &mut evaluation.mismatches,
    );
    consumers.sort();
    consumers.dedup();

    let providers = evidence
        .iter()
        .map(|item| item.transport)
        .collect::<BTreeSet<_>>();
    let provider = providers
        .first()
        .filter(|_| providers.len() == 1)
        .copied()
        .unwrap_or(key.transport)
        .as_str();
    let paired = !producers.is_empty() && !consumers.is_empty();
    let verdict = match (paired, producers.is_empty(), consumers.is_empty()) {
        (true, _, _) => "MATCH",
        (false, true, true) => "CONFIGURATION_ONLY",
        _ => "UNPAIRED_STATIC_EVIDENCE",
    };
    let key_text = format!(
        "{}:{}:{}",
        key.transport.as_str(),
        key.entity.as_str(),
        key.resource
    );
    if paired {
        evaluation.matches.push(json!({
            "key": key_text,
            "producer_count": producers.len(),
            "consumer_count": consumers.len(),
            "routed_matches": routed
        }));
    } else if !producers.is_empty() || !consumers.is_empty() {
        evaluation.ambiguities.push(json!({
            "code": "UNPAIRED_STATIC_EVIDENCE",
            "key": key_text,
            "candidates": ["external_peer", "runtime_configuration", "repository_outside_scope"],
            "evidence": evidence.iter().take(per_contract).map(|item| item.to_json()).collect::<Vec<_>>()
        }));
    }
    evaluation.contracts.push(json!({
        "key": key_text,
        "transport": "event",
        "provider": provider,
        "contract_family": key.transport.as_str(),
        "provider_candidates": providers.iter().map(|provider| provider.as_str()).collect::<Vec<_>>(),
        "entity": key.entity.as_str(),
        "kind": key.entity.as_str(),
        "name": key.resource,
        "verdict": verdict,
        "matched": paired,
        "backend_contract": evidence.iter()
            .find(|item| item.repository == "backend")
            .map(|item| item.to_json()),
        "callsites": evidence.iter()
            .filter(|item| item.repository != "backend")
            .take(per_contract)
            .map(|item| item.to_json())
            .collect::<Vec<_>>(),
        "producers": producers.into_iter().take(per_contract).map(Observation::to_json).collect::<Vec<_>>(),
        "consumers": consumers.into_iter().take(per_contract).map(Observation::to_json).collect::<Vec<_>>(),
        "configuration": declarations.into_iter().take(per_contract).map(Observation::to_json).collect::<Vec<_>>()
    }));
}

fn route_amqp_consumers<'a>(
    key: &ContractKey,
    producers: &[&'a Observation],
    bindings: &[&'a Observation],
    observations: &'a [Observation],
    consumers: &mut Vec<&'a Observation>,
    mismatches: &mut Vec<Value>,
) -> Vec<Value> {
    let mut routed = Vec::new();
    if key.transport != Transport::Amqp || key.entity != Entity::Exchange {
        return routed;
    }
    for producer in producers {
        for binding in bindings {
            if binding.exchange.as_deref() != Some(key.resource.as_str()) {
                continue;
            }
            let compatible = routing_keys_match(
                producer.routing_key.as_deref(),
                binding.routing_key.as_deref(),
            );
            let queue = binding.resource.as_deref().unwrap_or_default();
            for candidate in observations.iter().filter(|candidate| {
                matches!(candidate.transport, Transport::Amqp | Transport::RabbitMq)
                    && candidate.entity == Entity::Queue
                    && candidate.role == Role::Consumer
                    && candidate.resource.as_deref() == Some(queue)
            }) {
                if compatible {
                    consumers.push(candidate);
                    routed.push(json!({
                        "producer": producer.to_json(),
                        "binding": binding.to_json(),
                        "consumer": candidate.to_json(),
                        "routing": "COMPATIBLE"
                    }));
                } else {
                    mismatches.push(json!({
                        "code": "AMQP_ROUTING_KEY_MISMATCH",
                        "exchange": key.resource,
                        "producer": producer.to_json(),
                        "binding": binding.to_json(),
                        "consumer": candidate.to_json()
                    }));
                }
            }
        }
    }
    routed
}

pub(super) fn add_cross_kind_mismatches(observations: &[Observation], mismatches: &mut Vec<Value>) {
    let producers = observations
        .iter()
        .filter(|item| item.role == Role::Producer)
        .filter_map(|item| item.resource.as_deref().map(|resource| (item, resource)));
    for (producer, resource) in producers {
        for consumer in observations.iter().filter(|item| {
            item.role == Role::Consumer && item.resource.as_deref() == Some(resource)
        }) {
            if producer.transport.contract_family() != consumer.transport.contract_family() {
                mismatches.push(json!({
                    "code": "TRANSPORT_MISMATCH",
                    "resource": resource,
                    "producer": producer.to_json(),
                    "consumer": consumer.to_json()
                }));
            } else if producer.transport.contract_family() == consumer.transport.contract_family()
                && producer.entity != consumer.entity
                && !matches!(
                    (
                        producer.transport.contract_family(),
                        producer.entity,
                        consumer.entity
                    ),
                    (Transport::Jms, Entity::Destination, _)
                        | (Transport::Jms, _, Entity::Destination)
                )
            {
                mismatches.push(json!({
                    "code": "DESTINATION_KIND_MISMATCH",
                    "resource": resource,
                    "producer": producer.to_json(),
                    "consumer": consumer.to_json()
                }));
            }
        }
    }
    mismatches.sort_by(|left, right| {
        left["code"]
            .as_str()
            .unwrap_or_default()
            .cmp(right["code"].as_str().unwrap_or_default())
            .then_with(|| {
                left["resource"]
                    .as_str()
                    .unwrap_or_default()
                    .cmp(right["resource"].as_str().unwrap_or_default())
            })
    });
    mismatches.dedup();
}

pub(super) fn routing_keys_match(producer: Option<&str>, binding: Option<&str>) -> bool {
    let Some(producer) = producer else {
        return false;
    };
    let Some(binding) = binding else {
        return false;
    };
    if producer == binding {
        return true;
    }
    let producer = producer.split('.').collect::<Vec<_>>();
    let binding = binding.split('.').collect::<Vec<_>>();
    let mut left = 0_usize;
    let mut right = 0_usize;
    while left < producer.len() && right < binding.len() {
        match binding[right] {
            "#" => return true,
            "*" => {
                left += 1;
                right += 1;
            }
            exact if exact == producer[left] => {
                left += 1;
                right += 1;
            }
            _ => return false,
        }
    }
    (left == producer.len() && right == binding.len())
        || (right + 1 == binding.len() && binding[right] == "#")
}