use serde::ser::SerializeStruct;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub(crate) const INVALID_USE_CURVE_INTERVAL: &str = "tcoedge-use-curve-invalid-interval";
#[derive(Default)]
pub struct Stats {
pub missing_face_surface_kinds: std::collections::BTreeMap<String, usize>,
pub unknown_surface_kinds: std::collections::BTreeMap<String, usize>,
pub mesh_surface_faces: usize,
pub nurbs_surfaces: usize,
pub nurbs_curves: usize,
pub procedural_curve_kinds: std::collections::BTreeMap<String, usize>,
pub undecoded_pcurve_kinds: std::collections::BTreeMap<String, usize>,
pub partial_procedural_supports: usize,
pub other_record_kinds: std::collections::BTreeMap<String, usize>,
}
impl Stats {
#[must_use]
pub fn missing_face_surfaces(&self) -> usize {
self.missing_face_surface_kinds.values().sum()
}
#[must_use]
pub fn unknown_surface_faces(&self) -> usize {
self.unknown_surface_kinds.values().sum()
}
#[must_use]
pub fn procedural_curve_edges(&self) -> usize {
self.procedural_curve_kinds.values().sum()
}
#[must_use]
pub fn undecoded_pcurve_refs(&self) -> usize {
self.undecoded_pcurve_kinds.values().sum()
}
#[must_use]
pub fn invalid_use_curve_intervals(&self) -> usize {
self.other_record_kinds
.get(INVALID_USE_CURVE_INTERVAL)
.copied()
.unwrap_or(0)
}
#[must_use]
pub fn other_records(&self) -> usize {
self.other_record_kinds.values().sum()
}
pub(super) fn merge(&mut self, other: Self) {
macro_rules! add_counts {
($($field:ident),+ $(,)?) => {
$(self.$field += other.$field;)+
};
}
add_counts!(
mesh_surface_faces,
nurbs_surfaces,
nurbs_curves,
partial_procedural_supports,
);
for (target, source) in [
(
&mut self.missing_face_surface_kinds,
other.missing_face_surface_kinds,
),
(&mut self.unknown_surface_kinds, other.unknown_surface_kinds),
(
&mut self.procedural_curve_kinds,
other.procedural_curve_kinds,
),
(
&mut self.undecoded_pcurve_kinds,
other.undecoded_pcurve_kinds,
),
(&mut self.other_record_kinds, other.other_record_kinds),
] {
for (kind, count) in source {
*target.entry(kind).or_default() += count;
}
}
}
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct StatsWire {
missing_face_surfaces: usize,
#[serde(deserialize_with = "cadmpeg_core::distinct_keys::btree_map")]
missing_face_surface_kinds: std::collections::BTreeMap<String, usize>,
unknown_surface_faces: usize,
#[serde(deserialize_with = "cadmpeg_core::distinct_keys::btree_map")]
unknown_surface_kinds: std::collections::BTreeMap<String, usize>,
mesh_surface_faces: usize,
nurbs_surfaces: usize,
nurbs_curves: usize,
procedural_curve_edges: usize,
#[serde(deserialize_with = "cadmpeg_core::distinct_keys::btree_map")]
procedural_curve_kinds: std::collections::BTreeMap<String, usize>,
undecoded_pcurve_refs: usize,
#[serde(deserialize_with = "cadmpeg_core::distinct_keys::btree_map")]
undecoded_pcurve_kinds: std::collections::BTreeMap<String, usize>,
partial_procedural_supports: usize,
other_records: usize,
#[serde(deserialize_with = "cadmpeg_core::distinct_keys::btree_map")]
other_record_kinds: std::collections::BTreeMap<String, usize>,
}
impl Serialize for Stats {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let mut wire = serializer.serialize_struct("Stats", 14)?;
wire.serialize_field("missing_face_surfaces", &self.missing_face_surfaces())?;
wire.serialize_field(
"missing_face_surface_kinds",
&self.missing_face_surface_kinds,
)?;
wire.serialize_field("unknown_surface_faces", &self.unknown_surface_faces())?;
wire.serialize_field("unknown_surface_kinds", &self.unknown_surface_kinds)?;
wire.serialize_field("mesh_surface_faces", &self.mesh_surface_faces)?;
wire.serialize_field("nurbs_surfaces", &self.nurbs_surfaces)?;
wire.serialize_field("nurbs_curves", &self.nurbs_curves)?;
wire.serialize_field("procedural_curve_edges", &self.procedural_curve_edges())?;
wire.serialize_field("procedural_curve_kinds", &self.procedural_curve_kinds)?;
wire.serialize_field("undecoded_pcurve_refs", &self.undecoded_pcurve_refs())?;
wire.serialize_field("undecoded_pcurve_kinds", &self.undecoded_pcurve_kinds)?;
wire.serialize_field(
"partial_procedural_supports",
&self.partial_procedural_supports,
)?;
wire.serialize_field("other_records", &self.other_records())?;
wire.serialize_field("other_record_kinds", &self.other_record_kinds)?;
wire.end()
}
}
impl<'de> Deserialize<'de> for Stats {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let wire = StatsWire::deserialize(deserializer)?;
for (field, map_field, count, kinds) in [
(
"missing_face_surfaces",
"missing_face_surface_kinds",
wire.missing_face_surfaces,
&wire.missing_face_surface_kinds,
),
(
"unknown_surface_faces",
"unknown_surface_kinds",
wire.unknown_surface_faces,
&wire.unknown_surface_kinds,
),
(
"procedural_curve_edges",
"procedural_curve_kinds",
wire.procedural_curve_edges,
&wire.procedural_curve_kinds,
),
(
"undecoded_pcurve_refs",
"undecoded_pcurve_kinds",
wire.undecoded_pcurve_refs,
&wire.undecoded_pcurve_kinds,
),
(
"other_records",
"other_record_kinds",
wire.other_records,
&wire.other_record_kinds,
),
] {
if kinds
.values()
.try_fold(0_usize, |sum, value| sum.checked_add(*value))
!= Some(count)
{
return Err(serde::de::Error::custom(format!(
"{field} must equal the sum of {map_field}"
)));
}
}
Ok(Self {
missing_face_surface_kinds: wire.missing_face_surface_kinds,
unknown_surface_kinds: wire.unknown_surface_kinds,
mesh_surface_faces: wire.mesh_surface_faces,
nurbs_surfaces: wire.nurbs_surfaces,
nurbs_curves: wire.nurbs_curves,
procedural_curve_kinds: wire.procedural_curve_kinds,
undecoded_pcurve_kinds: wire.undecoded_pcurve_kinds,
partial_procedural_supports: wire.partial_procedural_supports,
other_record_kinds: wire.other_record_kinds,
})
}
}
#[cfg(test)]
mod tests {
use super::Stats;
fn document(kinds: &str) -> String {
format!(
"{{\"missing_face_surfaces\":3,\
\"missing_face_surface_kinds\":{kinds},\
\"unknown_surface_faces\":0,\"unknown_surface_kinds\":{{}},\
\"mesh_surface_faces\":0,\"nurbs_surfaces\":0,\"nurbs_curves\":0,\
\"procedural_curve_edges\":0,\"procedural_curve_kinds\":{{}},\
\"undecoded_pcurve_refs\":0,\"undecoded_pcurve_kinds\":{{}},\
\"partial_procedural_supports\":0,\
\"other_records\":0,\"other_record_kinds\":{{}}}}"
)
}
#[test]
fn a_restated_stats_kind_key_is_refused_by_name() {
let read: Stats = serde_json::from_str(&document("{\"cone\":1,\"plane\":2}"))
.expect("distinct kind keys");
assert_eq!(read.missing_face_surfaces(), 3);
let Err(error) = serde_json::from_str::<Stats>(&document("{\"cone\":1,\"cone\":3}")) else {
panic!("a restated kind key is refused")
};
let error = error.to_string();
assert!(error.contains("cone"), "{error}");
}
}