use code_ranker_plugin_api::{
attrs::ValueType,
level::{AttributeSpec, Direction, Thresholds},
};
use serde::Deserialize;
use std::sync::LazyLock;
#[derive(Debug, Clone, Copy, Default, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum Scope {
#[default]
Node,
Graph,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct MetricDef {
pub formula_cel: String,
#[serde(default)]
pub scope: Scope,
#[serde(default = "default_value_type")]
pub value_type: String,
pub label: Option<String>,
pub name: Option<String>,
pub short: Option<String>,
pub description: Option<String>,
pub remediation: Option<String>,
pub formula_pretty: Option<String>,
pub formula_js: Option<String>,
pub direction: Option<String>,
pub group: Option<String>,
#[serde(default = "default_omit_at")]
pub omit_at: f64,
pub warning: Option<f64>,
pub info: Option<f64>,
}
static FIELD_DEFAULTS: LazyLock<FieldDefaults> = LazyLock::new(|| {
#[derive(Deserialize)]
struct Wrap {
defaults: FieldDefaults,
}
toml::from_str::<Wrap>(include_str!("../../metrics/builtin.toml"))
.expect("metrics/builtin.toml [defaults] parses")
.defaults
});
#[derive(Debug, Clone, Deserialize)]
struct FieldDefaults {
value_type: String,
omit_at: f64,
}
pub(crate) fn default_value_type() -> String {
FIELD_DEFAULTS.value_type.clone()
}
pub(crate) fn default_omit_at() -> f64 {
FIELD_DEFAULTS.omit_at
}
impl MetricDef {
fn thresholds(&self) -> Option<Thresholds> {
match (self.warning, self.info) {
(None, None) => None,
(w, i) => Some(Thresholds {
warning: w.or(i).unwrap_or(0.0),
info: i.or(w).unwrap_or(0.0),
}),
}
}
pub fn to_attribute_spec(&self) -> AttributeSpec {
let value_type = match self.value_type.as_str() {
"int" => ValueType::Int,
"bool" => ValueType::Bool,
"str" | "string" => ValueType::Str,
_ => ValueType::Float,
};
let direction = match self.direction.as_deref() {
Some("lower_better") => Direction::LowerBetter,
Some("higher_better") => Direction::HigherBetter,
_ => Direction::Neutral,
};
AttributeSpec {
value_type,
label: self.label.clone(),
name: self.name.clone(),
short: self.short.clone(),
description: self.description.clone(),
remediation: self.remediation.clone(),
formula: self.formula_pretty.clone(),
calc: self
.formula_js
.clone()
.or_else(|| (self.scope == Scope::Node).then(|| self.formula_cel.clone())),
direction,
abbreviate: None,
group: self.group.clone(),
thresholds: self.thresholds(),
omit_at: self.omit_at,
}
}
}
#[derive(Debug)]
pub enum RegistryError {
Parse { key: String, message: String },
Cycle { keys: Vec<String> },
}
impl std::fmt::Display for RegistryError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
RegistryError::Parse { key, message } => {
write!(f, "metric `{key}`: invalid CEL formula: {message}")
}
RegistryError::Cycle { keys } => {
write!(
f,
"metric formulas form a dependency cycle: {}",
keys.join(" → ")
)
}
}
}
}
impl std::error::Error for RegistryError {}