use std::collections::BTreeMap;
use std::fmt;
use cadmpeg_core::dialect::DialectId;
#[cfg(feature = "schema")]
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::document::CensusKey;
use crate::report::loss::LossNote;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct ExportReport {
identity: ExportIdentity,
pub census: EntityCensus,
write_path: WritePath,
pub losses: Vec<LossNote>,
pub notes: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "payload", rename_all = "snake_case", deny_unknown_fields)]
enum ExportIdentity {
Cadir {},
Native {
target: DialectId,
},
}
#[cfg(all(test, feature = "schema"))]
mod schema_tests {
#[test]
fn the_native_export_payload_schema_requires_its_target() {
let schema = serde_json::to_value(schemars::schema_for!(super::ExportIdentity))
.expect("export identity schema serializes");
let native = schema["oneOf"]
.as_array()
.expect("export identity schema is a tagged union")
.iter()
.find(|arm| arm["properties"]["payload"]["const"] == "native")
.expect("the native arm");
let required = native["required"]
.as_array()
.expect("the native arm has required fields");
assert!(required.iter().any(|field| field == "target"), "{schema:#}");
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "path", rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
pub enum WritePath {
VerbatimReplay {
fidelity: ReplayFidelity,
},
Patched {
fidelity: FidelityResolution,
},
Synthesized {
fidelity: SynthesisFidelity,
},
}
impl fmt::Display for WritePath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::VerbatimReplay { .. } => "verbatim_replay",
Self::Patched { .. } => "patched",
Self::Synthesized { .. } => "synthesized",
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "snake_case", tag = "status")]
#[serde(deny_unknown_fields)]
pub enum ReplayFidelity {
NotProvided {},
Replayed {},
}
impl From<ReplayFidelity> for FidelityResolution {
fn from(fidelity: ReplayFidelity) -> Self {
match fidelity {
ReplayFidelity::NotProvided {} => Self::NotProvided {},
ReplayFidelity::Replayed {} => Self::Replayed {},
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "snake_case", tag = "status")]
#[serde(deny_unknown_fields)]
pub enum SynthesisFidelity {
NotProvided {},
NotConsumed {},
Degraded {
reason: String,
},
}
impl From<SynthesisFidelity> for FidelityResolution {
fn from(fidelity: SynthesisFidelity) -> Self {
match fidelity {
SynthesisFidelity::NotProvided {} => Self::NotProvided {},
SynthesisFidelity::NotConsumed {} => Self::NotConsumed {},
SynthesisFidelity::Degraded { reason } => Self::Degraded { reason },
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "snake_case", tag = "status")]
#[serde(deny_unknown_fields)]
pub enum FidelityResolution {
NotProvided {},
Replayed {},
NotConsumed {},
Degraded {
reason: String,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
pub enum CensusBasis {
TargetRecords,
IrArenas,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct EntityCensus {
pub basis: CensusBasis,
#[serde(deserialize_with = "cadmpeg_core::distinct_keys::btree_map")]
pub counts: BTreeMap<CensusKey, usize>,
}
impl EntityCensus {
pub fn total(&self) -> usize {
self.counts.values().sum()
}
}
impl ExportReport {
#[must_use]
pub fn write_path(&self) -> &WritePath {
&self.write_path
}
#[must_use]
pub fn format(&self) -> &str {
match &self.identity {
ExportIdentity::Cadir {} => "cadir",
ExportIdentity::Native { target } => target.namespace(),
}
}
#[must_use]
pub(crate) fn cadir(
census: EntityCensus,
write_path: WritePath,
losses: Vec<LossNote>,
notes: Vec<String>,
) -> Self {
Self {
identity: ExportIdentity::Cadir {},
census,
write_path,
losses,
notes,
}
}
#[must_use]
pub(crate) fn native(
target: DialectId,
census: EntityCensus,
write_path: WritePath,
losses: Vec<LossNote>,
notes: Vec<String>,
) -> Self {
Self {
identity: ExportIdentity::Native { target },
census,
write_path,
losses,
notes,
}
}
}
#[cfg(test)]
mod tests;