1use std::collections::BTreeMap;
4
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
9#[serde(rename_all = "camelCase")]
10pub enum LossKind {
11 Constraints,
13 PluginGeometry,
15 Dimensions,
17 Approximated,
19 Unsupported,
21 Style,
23 Text,
25 ExternalReference,
27 Units,
29 Fallback,
31}
32
33#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
35#[serde(rename_all = "camelCase")]
36pub struct Loss {
37 pub kind: LossKind,
39 pub what: String,
41 pub count: usize,
43}
44
45#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
47#[serde(rename_all = "camelCase")]
48pub struct ConversionReport {
49 pub entities: usize,
51 pub losses: Vec<Loss>,
53 pub notes: Vec<String>,
55}
56
57#[derive(Debug, Default)]
59pub(crate) struct Recorder {
60 counts: BTreeMap<(LossKind, String), usize>,
61 pub notes: Vec<String>,
62 pub entities: usize,
63}
64
65impl Recorder {
66 pub fn add(&mut self, kind: LossKind, what: impl Into<String>) {
67 *self.counts.entry((kind, what.into())).or_insert(0) += 1;
68 }
69
70 pub fn finish(self) -> ConversionReport {
71 ConversionReport {
72 entities: self.entities,
73 losses: self.counts.into_iter().map(|((kind, what), count)| Loss { kind, what, count }).collect(),
74 notes: self.notes,
75 }
76 }
77}
78
79impl ConversionReport {
80 #[must_use]
82 pub fn is_lossless(&self) -> bool {
83 self.losses.is_empty()
84 }
85
86 #[must_use]
88 pub fn count(&self, kind: LossKind) -> usize {
89 self.losses.iter().filter(|l| l.kind == kind).map(|l| l.count).sum()
90 }
91}