use crate::attrs::ValueType;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EdgeKindSpec {
pub flow: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub label: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AttributeGroup {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub label: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct Thresholds {
pub info: f64,
pub warning: f64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Direction {
#[default]
Neutral,
LowerBetter,
HigherBetter,
}
impl Direction {
pub fn is_neutral(&self) -> bool {
matches!(self, Direction::Neutral)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AttributeSpec {
pub value_type: ValueType,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub label: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub short: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub formula: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub calc: Option<String>,
#[serde(default, skip_serializing_if = "Direction::is_neutral")]
pub direction: Direction,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub abbreviate: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub group: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub thresholds: Option<Thresholds>,
#[serde(default, skip_serializing_if = "f64_is_zero")]
pub omit_at: f64,
}
fn f64_is_zero(v: &f64) -> bool {
*v == 0.0
}
impl AttributeSpec {
pub fn new(value_type: ValueType, label: &str) -> Self {
Self {
value_type,
label: Some(label.to_string()),
name: None,
short: None,
description: None,
formula: None,
calc: None,
direction: Direction::Neutral,
abbreviate: None,
group: None,
thresholds: None,
omit_at: 0.0,
}
}
}
#[derive(Clone)]
pub struct SpecRow {
pub group: &'static str,
pub value_type: ValueType,
pub label: &'static str,
pub name: &'static str,
pub short: &'static str,
pub description: &'static str,
pub formula: &'static str,
pub calc: &'static str,
pub direction: Direction,
pub abbreviate: bool,
pub omit_at: f64,
}
impl Default for SpecRow {
fn default() -> Self {
SpecRow {
group: "",
value_type: ValueType::Int,
label: "",
name: "",
short: "",
description: "",
formula: "",
calc: "",
direction: Direction::Neutral,
abbreviate: false,
omit_at: 0.0,
}
}
}
impl SpecRow {
fn into_spec(self) -> AttributeSpec {
let opt = |s: &str| (!s.is_empty()).then(|| s.to_string());
AttributeSpec {
value_type: self.value_type,
label: opt(self.label),
name: opt(self.name),
short: opt(self.short),
description: opt(self.description),
formula: opt(self.formula),
calc: opt(self.calc),
direction: self.direction,
abbreviate: self.abbreviate.then_some(true),
group: opt(self.group),
thresholds: None,
omit_at: self.omit_at,
}
}
}
pub fn attr_dict(rows: Vec<(&'static str, SpecRow)>) -> BTreeMap<String, AttributeSpec> {
rows.into_iter()
.map(|(k, r)| (k.to_string(), r.into_spec()))
.collect()
}
pub fn group(label: &str, description: &str) -> AttributeGroup {
AttributeGroup {
label: Some(label.to_string()),
description: Some(description.to_string()),
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NodeKindSpec {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub label: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub plural: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub fill: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub stroke: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub external: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CycleKindSpec {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub label: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Grouping {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub key: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub function: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Level {
pub name: String,
pub edge_kinds: BTreeMap<String, EdgeKindSpec>,
pub node_attributes: BTreeMap<String, AttributeSpec>,
pub edge_attributes: BTreeMap<String, AttributeSpec>,
pub attribute_groups: BTreeMap<String, AttributeGroup>,
#[serde(default)]
pub node_kinds: BTreeMap<String, NodeKindSpec>,
#[serde(default)]
pub cycle_kinds: BTreeMap<String, CycleKindSpec>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub grouping: Option<Grouping>,
}