use super::{Change, Contract, Severity};
use crate::diff::{
Deltas,
json::{JsonDeltas, JsonGenerator},
};
use serde::{Deserialize, Serialize};
use std::collections::BTreeSet;
#[derive(Debug, Serialize, Deserialize)]
pub struct Side {
pub file_sha256: String,
pub contract: Contract,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Summary {
pub operations_added: usize,
pub operations_removed: usize,
pub operations_changed: usize,
pub breaking: usize,
pub non_breaking: usize,
pub review: usize,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct JsonDiff {
pub schema_version: u8,
pub generator: JsonGenerator,
pub old: Side,
pub new: Side,
pub summary: Summary,
pub changes: Vec<Change>,
#[serde(skip_serializing_if = "Option::is_none")]
pub deltas: Option<JsonDeltas>,
}
impl JsonDiff {
pub fn has_breaking(&self) -> bool {
self.summary.breaking > 0
}
}
pub fn openapi(
old: &crate::ApiDocumentation,
new: &crate::ApiDocumentation,
deltas: Option<&Deltas>,
old_sha: &str,
new_sha: &str,
) -> anyhow::Result<JsonDiff> {
let legacy = crate::diff::json::to_json(&crate::diff::diff(old, new), deltas, old_sha, new_sha);
Ok(to_json(
super::openapi::adapt(old),
super::openapi::adapt(new),
super::openapi::diff(old, new)?,
old_sha,
new_sha,
legacy.deltas,
))
}
pub fn to_json(
old: Contract,
new: Contract,
changes: Vec<Change>,
old_sha: &str,
new_sha: &str,
deltas: Option<JsonDeltas>,
) -> JsonDiff {
let entities: std::collections::HashMap<_, _> = old
.entities
.iter()
.chain(&new.entities)
.map(|e| (e.id.as_str(), e))
.collect();
let surviving: BTreeSet<_> = new
.entities
.iter()
.filter(|e| {
e.kind == super::EntityKind::Operation && old.entities.iter().any(|o| o.id == e.id)
})
.map(|e| e.id.as_str())
.collect();
let operations_changed = changes
.iter()
.filter_map(|change| {
if matches!(
change.kind,
super::ChangeKind::OperationAdded | super::ChangeKind::OperationRemoved
) {
return None;
}
let mut id = change.subject.as_str();
let mut visited = BTreeSet::new();
while visited.insert(id) {
let entity = entities.get(id)?;
if entity.kind == super::EntityKind::Operation {
return surviving.contains(id).then_some(id);
}
id = entity.parent.as_deref()?;
}
None
})
.collect::<BTreeSet<_>>()
.len();
let summary = Summary {
operations_added: changes
.iter()
.filter(|c| c.kind == super::ChangeKind::OperationAdded)
.count(),
operations_removed: changes
.iter()
.filter(|c| c.kind == super::ChangeKind::OperationRemoved)
.count(),
operations_changed,
breaking: changes
.iter()
.filter(|c| c.severity == Severity::Breaking)
.count(),
non_breaking: changes
.iter()
.filter(|c| c.severity == Severity::NonBreaking)
.count(),
review: changes
.iter()
.filter(|c| c.severity == Severity::Review)
.count(),
};
JsonDiff {
schema_version: 2,
generator: JsonGenerator {
name: "vimanam".into(),
version: env!("CARGO_PKG_VERSION").into(),
},
old: Side {
file_sha256: old_sha.into(),
contract: old,
},
new: Side {
file_sha256: new_sha.into(),
contract: new,
},
summary,
changes,
deltas,
}
}