use serde::Serialize;
use std::collections::BTreeMap;
use std::fmt;
use crate::evaluation::EvaluationScope;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum Severity {
Note,
Warning,
Error,
}
impl fmt::Display for Severity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Severity::Note => "note",
Severity::Warning => "warning",
Severity::Error => "error",
})
}
}
#[derive(Debug, Clone, Serialize)]
#[serde(untagged)]
#[non_exhaustive]
pub enum Value {
Number(f64),
Text(String),
}
#[derive(Debug, Clone, Serialize)]
#[non_exhaustive]
pub struct MemberMeasurement {
pub member: String,
pub measurements: BTreeMap<String, Value>,
}
impl MemberMeasurement {
pub fn new(member: impl Into<String>) -> Self {
Self {
member: member.into(),
measurements: BTreeMap::new(),
}
}
pub fn measurement(mut self, name: impl Into<String>, value: impl Into<Value>) -> Self {
let value = value.into();
if value.is_finite() {
self.measurements.insert(name.into(), value);
}
self
}
}
impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Value::Number(n) => write!(f, "{n:.4}"),
Value::Text(s) => f.write_str(s),
}
}
}
#[derive(Debug, Clone, Serialize)]
#[non_exhaustive]
pub struct Finding {
pub check_id: &'static str,
pub severity: Severity,
#[serde(skip_serializing_if = "Option::is_none")]
pub clip: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub bone: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub node: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub prediction_scope: Option<EvaluationScope>,
#[serde(skip_serializing_if = "non_finite_time_or_none")]
pub time_s: Option<f32>,
#[serde(skip_serializing_if = "non_finite_value_or_none")]
pub measured: Option<Value>,
#[serde(skip_serializing_if = "non_finite_value_or_none")]
pub expected: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub members: Option<Vec<MemberMeasurement>>,
pub message: String,
}
impl Finding {
pub fn new(check_id: &'static str, severity: Severity, message: impl Into<String>) -> Self {
Self {
check_id,
severity,
clip: None,
bone: None,
node: None,
prediction_scope: None,
time_s: None,
measured: None,
expected: None,
members: None,
message: message.into(),
}
}
pub fn clip(mut self, clip: impl Into<String>) -> Self {
self.clip = Some(clip.into());
self
}
pub fn bone(mut self, bone: impl Into<String>) -> Self {
self.bone = Some(bone.into());
self
}
pub fn node(mut self, node: impl Into<String>) -> Self {
self.node = Some(node.into());
self
}
pub fn prediction_scope(mut self, scope: EvaluationScope) -> Self {
self.prediction_scope = Some(scope);
self
}
pub fn time(mut self, t: f32) -> Self {
self.time_s = t.is_finite().then_some(t);
self
}
pub fn measured(mut self, v: impl Into<Value>) -> Self {
let value = v.into();
self.measured = value.is_finite().then_some(value);
self
}
pub fn expected(mut self, v: impl Into<Value>) -> Self {
let value = v.into();
self.expected = value.is_finite().then_some(value);
self
}
pub fn members(mut self, mut members: Vec<MemberMeasurement>) -> Self {
for member in &mut members {
member.measurements.retain(|_, value| value.is_finite());
}
self.members = Some(members);
self
}
}
impl Value {
fn is_finite(&self) -> bool {
match self {
Self::Number(number) => number.is_finite(),
Self::Text(_) => true,
}
}
}
fn non_finite_time_or_none(value: &Option<f32>) -> bool {
value.is_none_or(|time| !time.is_finite())
}
fn non_finite_value_or_none(value: &Option<Value>) -> bool {
value.as_ref().is_none_or(|value| !value.is_finite())
}
impl From<f64> for Value {
fn from(n: f64) -> Self {
Value::Number(n)
}
}
impl From<f32> for Value {
fn from(n: f32) -> Self {
Value::Number(n as f64)
}
}
impl From<&str> for Value {
fn from(s: &str) -> Self {
Value::Text(s.to_owned())
}
}
impl From<String> for Value {
fn from(s: String) -> Self {
Value::Text(s)
}
}