use dashmap::DashMap;
use semdiff_core::Reporter;
use serde::Serialize;
use serde_json::Value;
use std::collections::BTreeMap;
use std::io::Write;
use std::sync::atomic::{AtomicUsize, Ordering};
pub struct JsonReport<W> {
writer: W,
unchanged: AtomicUsize,
modified: AtomicUsize,
added: AtomicUsize,
deleted: AtomicUsize,
entries: DashMap<String, JsonReportEntry>,
}
impl<W> JsonReport<W> {
pub fn new(writer: W) -> JsonReport<W> {
JsonReport {
writer,
unchanged: AtomicUsize::new(0),
modified: AtomicUsize::new(0),
added: AtomicUsize::new(0),
deleted: AtomicUsize::new(0),
entries: DashMap::new(),
}
}
pub fn record_unchanged(&self, name: &str, compares: &'static str, additional: impl Serialize) {
self.unchanged.fetch_add(1, Ordering::Relaxed);
self.insert_entry(
name,
JsonReportEntry::new(JsonEntryStatus::Unchanged, compares, additional),
);
}
pub fn record_modified(&self, name: &str, compares: &'static str, additional: impl Serialize) {
self.modified.fetch_add(1, Ordering::Relaxed);
self.insert_entry(
name,
JsonReportEntry::new(JsonEntryStatus::Modified, compares, additional),
);
}
pub fn record_added(&self, name: &str, compares: &'static str, additional: impl Serialize) {
self.added.fetch_add(1, Ordering::Relaxed);
self.insert_entry(name, JsonReportEntry::new(JsonEntryStatus::Added, compares, additional));
}
pub fn record_deleted(&self, name: &str, compares: &'static str, additional: impl Serialize) {
self.deleted.fetch_add(1, Ordering::Relaxed);
self.insert_entry(
name,
JsonReportEntry::new(JsonEntryStatus::Deleted, compares, additional),
);
}
fn insert_entry(&self, name: &str, entry: JsonReportEntry) {
let key = name.to_owned();
assert!(self.entries.insert(key, entry).is_none());
}
}
#[derive(Serialize)]
struct JsonReportOutput {
unchanged: usize,
modified: usize,
added: usize,
deleted: usize,
entries: BTreeMap<String, JsonReportEntry>,
}
#[derive(Serialize)]
struct JsonReportEntry {
status: JsonEntryStatus,
compares: &'static str,
#[serde(flatten)]
additional: Value,
}
impl JsonReportEntry {
fn new(status: JsonEntryStatus, compares: &'static str, additional: impl Serialize) -> JsonReportEntry {
let additional = serde_json::to_value(additional).unwrap();
JsonReportEntry {
status,
compares,
additional,
}
}
}
#[derive(Serialize)]
#[serde(rename_all = "lowercase")]
enum JsonEntryStatus {
Unchanged,
Modified,
Added,
Deleted,
}
impl<W: Write> Reporter for JsonReport<W> {
type Error = serde_json::Error;
fn start(&mut self) -> Result<(), Self::Error> {
Ok(())
}
fn finish(self) -> Result<(), Self::Error> {
let JsonReport {
mut writer,
unchanged,
modified,
added,
deleted,
entries,
} = self;
let output = JsonReportOutput {
unchanged: unchanged.into_inner(),
modified: modified.into_inner(),
added: added.into_inner(),
deleted: deleted.into_inner(),
entries: BTreeMap::from_iter(entries),
};
serde_json::to_writer_pretty(&mut writer, &output)
}
}