animsmith_core/
finding.rs1use serde::Serialize;
6use std::collections::BTreeMap;
7use std::fmt;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize)]
11#[serde(rename_all = "lowercase")]
12pub enum Severity {
13 Note,
15 Warning,
18 Error,
21}
22
23impl fmt::Display for Severity {
24 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25 f.write_str(match self {
26 Severity::Note => "note",
27 Severity::Warning => "warning",
28 Severity::Error => "error",
29 })
30 }
31}
32
33#[derive(Debug, Clone, Serialize)]
35#[serde(untagged)]
36#[non_exhaustive]
37pub enum Value {
38 Number(f64),
40 Text(String),
42}
43
44#[derive(Debug, Clone, Serialize)]
51#[non_exhaustive]
52pub struct MemberMeasurement {
53 pub member: String,
55 pub measurements: BTreeMap<String, Value>,
58}
59
60impl MemberMeasurement {
61 pub fn new(member: impl Into<String>) -> Self {
63 Self {
64 member: member.into(),
65 measurements: BTreeMap::new(),
66 }
67 }
68
69 pub fn measurement(mut self, name: impl Into<String>, value: impl Into<Value>) -> Self {
71 let value = value.into();
72 if value.is_finite() {
73 self.measurements.insert(name.into(), value);
74 }
75 self
76 }
77}
78
79impl fmt::Display for Value {
80 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81 match self {
82 Value::Number(n) => write!(f, "{n:.4}"),
83 Value::Text(s) => f.write_str(s),
84 }
85 }
86}
87
88#[derive(Debug, Clone, Serialize)]
95#[non_exhaustive]
96pub struct Finding {
97 pub check_id: &'static str,
99 pub severity: Severity,
101 #[serde(skip_serializing_if = "Option::is_none")]
103 pub clip: Option<String>,
104 #[serde(skip_serializing_if = "Option::is_none")]
106 pub bone: Option<String>,
107 #[serde(skip_serializing_if = "Option::is_none")]
111 pub node: Option<String>,
112 #[serde(skip_serializing_if = "non_finite_time_or_none")]
115 pub time_s: Option<f32>,
116 #[serde(skip_serializing_if = "non_finite_value_or_none")]
119 pub measured: Option<Value>,
120 #[serde(skip_serializing_if = "non_finite_value_or_none")]
123 pub expected: Option<Value>,
124 #[serde(skip_serializing_if = "Option::is_none")]
126 pub members: Option<Vec<MemberMeasurement>>,
127 pub message: String,
129}
130
131impl Finding {
132 pub fn new(check_id: &'static str, severity: Severity, message: impl Into<String>) -> Self {
134 Self {
135 check_id,
136 severity,
137 clip: None,
138 bone: None,
139 node: None,
140 time_s: None,
141 measured: None,
142 expected: None,
143 members: None,
144 message: message.into(),
145 }
146 }
147
148 pub fn clip(mut self, clip: impl Into<String>) -> Self {
150 self.clip = Some(clip.into());
151 self
152 }
153
154 pub fn bone(mut self, bone: impl Into<String>) -> Self {
156 self.bone = Some(bone.into());
157 self
158 }
159
160 pub fn node(mut self, node: impl Into<String>) -> Self {
162 self.node = Some(node.into());
163 self
164 }
165
166 pub fn time(mut self, t: f32) -> Self {
168 self.time_s = t.is_finite().then_some(t);
169 self
170 }
171
172 pub fn measured(mut self, v: impl Into<Value>) -> Self {
174 let value = v.into();
175 self.measured = value.is_finite().then_some(value);
176 self
177 }
178
179 pub fn expected(mut self, v: impl Into<Value>) -> Self {
181 let value = v.into();
182 self.expected = value.is_finite().then_some(value);
183 self
184 }
185
186 pub fn members(mut self, mut members: Vec<MemberMeasurement>) -> Self {
191 for member in &mut members {
192 member.measurements.retain(|_, value| value.is_finite());
193 }
194 self.members = Some(members);
195 self
196 }
197}
198
199impl Value {
200 fn is_finite(&self) -> bool {
201 match self {
202 Self::Number(number) => number.is_finite(),
203 Self::Text(_) => true,
204 }
205 }
206}
207
208fn non_finite_time_or_none(value: &Option<f32>) -> bool {
209 value.is_none_or(|time| !time.is_finite())
210}
211
212fn non_finite_value_or_none(value: &Option<Value>) -> bool {
213 value.as_ref().is_none_or(|value| !value.is_finite())
214}
215
216impl From<f64> for Value {
217 fn from(n: f64) -> Self {
218 Value::Number(n)
219 }
220}
221
222impl From<f32> for Value {
223 fn from(n: f32) -> Self {
224 Value::Number(n as f64)
225 }
226}
227
228impl From<&str> for Value {
229 fn from(s: &str) -> Self {
230 Value::Text(s.to_owned())
231 }
232}
233
234impl From<String> for Value {
235 fn from(s: String) -> Self {
236 Value::Text(s)
237 }
238}