use serde::{Deserialize, Serialize};
use crate::error::PluginError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum PluginComplianceStatus {
Compliant,
NonCompliant,
NotAssessed,
PassthroughNoValidation,
NotImplemented,
}
pub const METRIC_CO2E_SCORE: &str = "co2e_score";
pub const METRIC_REPAIRABILITY_INDEX: &str = "repairability_index";
pub const METRIC_RECYCLED_CONTENT_PCT: &str = "recycled_content_pct";
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PluginFinding {
pub code: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub field: String,
pub message: String,
}
impl PluginFinding {
pub fn new(
code: impl Into<String>,
field: impl Into<String>,
message: impl Into<String>,
) -> Self {
Self {
code: code.into(),
field: field.into(),
message: message.into(),
}
}
}
fn serialize_finite_metrics<S>(
metrics: &std::collections::HashMap<String, f64>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::{Error, SerializeMap};
let mut map = serializer.serialize_map(Some(metrics.len()))?;
for (key, value) in metrics {
if !value.is_finite() {
return Err(S::Error::custom(format!(
"metric '{key}' is not finite ({value})"
)));
}
map.serialize_entry(key, value)?;
}
map.end()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PluginResult {
pub compliance_status: PluginComplianceStatus,
#[serde(default, serialize_with = "serialize_finite_metrics")]
pub metrics: std::collections::HashMap<String, f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub extra: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub violations: Vec<PluginFinding>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub warnings: Vec<PluginFinding>,
}
impl PluginResult {
pub fn new(status: PluginComplianceStatus) -> Self {
Self {
compliance_status: status,
metrics: std::collections::HashMap::new(),
extra: None,
violations: Vec::new(),
warnings: Vec::new(),
}
}
pub fn with_metric(mut self, key: &str, value: f64) -> Self {
if value.is_finite() {
self.metrics.insert(key.to_owned(), value);
}
self
}
pub fn maybe_metric(mut self, key: &str, value: Option<f64>) -> Self {
if let Some(v) = value
&& v.is_finite()
{
self.metrics.insert(key.to_owned(), v);
}
self
}
pub fn with_extra(mut self, extra: serde_json::Value) -> Self {
self.extra = Some(extra);
self
}
pub fn with_violation(mut self, finding: PluginFinding) -> Self {
self.violations.push(finding);
self
}
pub fn with_warning(mut self, finding: PluginFinding) -> Self {
self.warnings.push(finding);
self
}
pub fn co2e_score(&self) -> Option<f64> {
self.metrics.get(METRIC_CO2E_SCORE).copied()
}
pub fn repairability_index(&self) -> Option<f64> {
self.metrics.get(METRIC_REPAIRABILITY_INDEX).copied()
}
pub fn recycled_content_pct(&self) -> Option<f64> {
self.metrics.get(METRIC_RECYCLED_CONTENT_PCT).copied()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AbiResult {
Ok(serde_json::Value),
Error(PluginError),
}
impl AbiResult {
pub fn ok<T: Serialize>(value: &T) -> Self {
match serde_json::to_value(value) {
Ok(v) => Self::Ok(v),
Err(e) => Self::Error(PluginError::Internal(format!(
"failed to serialise plugin result: {e}"
))),
}
}
pub fn is_ok(&self) -> bool {
matches!(self, Self::Ok(_))
}
}