use serde::Serialize;
use serde_json::Value;
use std::collections::BTreeSet;
pub mod async_sample;
mod asyncapi;
mod context;
mod delta;
mod operations;
mod projection;
mod references;
use delta::diff;
use operations::operations;
#[derive(Debug, Serialize)]
pub struct FieldDelta {
pub location: String,
pub change: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub mock: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub source: Option<Value>,
}
#[derive(Debug, Serialize)]
pub struct RouteFidelity {
pub endpoint: String,
pub status: String,
pub change: String,
pub deltas: Vec<FieldDelta>,
}
#[derive(Debug, Serialize)]
pub struct FidelityReport {
pub format_version: u8,
pub routes_with_drift: usize,
pub routes: Vec<RouteFidelity>,
}
pub fn compare_mock_spec(mock: &Value, source: &Value) -> Result<FidelityReport, String> {
compare_mock_spec_with_budget(mock, source, usize::MAX)
}
pub fn compare_mock_spec_with_budget(
mock: &Value,
source: &Value,
max_bytes: usize,
) -> Result<FidelityReport, String> {
let asynchronous = mock.get("asyncapi").is_some();
if asynchronous != source.get("asyncapi").is_some() {
return Err("Cannot compare OpenAPI and AsyncAPI definitions".into());
}
let (mock_routes, source_routes) = if asynchronous {
(asyncapi::operations(mock, max_bytes)?, asyncapi::operations(source, max_bytes)?)
} else {
(operations(mock, max_bytes)?, operations(source, max_bytes)?)
};
if mock_routes.is_empty() && source_routes.is_empty() {
return Err("neither definition contains an operation or channel to compare".into());
}
let endpoints: BTreeSet<_> = mock_routes.keys().chain(source_routes.keys()).collect();
let mut routes = Vec::with_capacity(endpoints.len());
for endpoint in endpoints {
let before = mock_routes.get(endpoint);
let after = source_routes.get(endpoint);
let mut deltas = Vec::new();
diff(before, after, "", &mut deltas);
let change = match (before, after, deltas.is_empty()) {
(_, _, true) => "unchanged",
(None, _, _) => "added",
(_, None, _) => "removed",
_ => "changed",
};
routes.push(RouteFidelity {
endpoint: endpoint.clone(),
status: if deltas.is_empty() {
"verified"
} else {
"drift-detected"
}
.into(),
change: change.into(),
deltas,
});
}
Ok(FidelityReport {
format_version: 1,
routes_with_drift: routes.iter().filter(|r| !r.deltas.is_empty()).count(),
routes,
})
}
#[cfg(test)]
mod tests;