1use crate::attrs::ValueType;
11use serde::{Deserialize, Serialize};
12use std::collections::BTreeMap;
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct EdgeKindSpec {
20 pub flow: bool,
21 #[serde(default, skip_serializing_if = "Option::is_none")]
22 pub label: Option<String>,
23 #[serde(default, skip_serializing_if = "Option::is_none")]
25 pub description: Option<String>,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct AttributeGroup {
33 #[serde(default, skip_serializing_if = "Option::is_none")]
34 pub label: Option<String>,
35 #[serde(default, skip_serializing_if = "Option::is_none")]
36 pub description: Option<String>,
37}
38
39#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
43pub struct Thresholds {
44 pub info: f64,
45 pub warning: f64,
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
54#[serde(rename_all = "snake_case")]
55pub enum Direction {
56 #[default]
57 Neutral,
58 LowerBetter,
59 HigherBetter,
60}
61
62impl Direction {
63 pub fn is_neutral(&self) -> bool {
65 matches!(self, Direction::Neutral)
66 }
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize)]
73pub struct AttributeSpec {
74 pub value_type: ValueType,
75 #[serde(default, skip_serializing_if = "Option::is_none")]
77 pub label: Option<String>,
78 #[serde(default, skip_serializing_if = "Option::is_none")]
80 pub name: Option<String>,
81 #[serde(default, skip_serializing_if = "Option::is_none")]
83 pub short: Option<String>,
84 #[serde(default, skip_serializing_if = "Option::is_none")]
86 pub description: Option<String>,
87 #[serde(default, skip_serializing_if = "Option::is_none")]
89 pub formula: Option<String>,
90 #[serde(default, skip_serializing_if = "Option::is_none")]
93 pub calc: Option<String>,
94 #[serde(default, skip_serializing_if = "Direction::is_neutral")]
97 pub direction: Direction,
98 #[serde(default, skip_serializing_if = "Option::is_none")]
100 pub abbreviate: Option<bool>,
101 #[serde(default, skip_serializing_if = "Option::is_none")]
103 pub group: Option<String>,
104 #[serde(default, skip_serializing_if = "Option::is_none")]
106 pub thresholds: Option<Thresholds>,
107}
108
109impl AttributeSpec {
110 pub fn new(value_type: ValueType, label: &str) -> Self {
112 Self {
113 value_type,
114 label: Some(label.to_string()),
115 name: None,
116 short: None,
117 description: None,
118 formula: None,
119 calc: None,
120 direction: Direction::Neutral,
121 abbreviate: None,
122 group: None,
123 thresholds: None,
124 }
125 }
126}
127
128#[derive(Clone)]
136pub struct SpecRow {
137 pub group: &'static str,
138 pub value_type: ValueType,
139 pub label: &'static str,
140 pub name: &'static str,
141 pub short: &'static str,
142 pub description: &'static str,
143 pub formula: &'static str,
144 pub calc: &'static str,
145 pub direction: Direction,
146 pub abbreviate: bool,
147}
148
149impl Default for SpecRow {
150 fn default() -> Self {
151 SpecRow {
152 group: "",
153 value_type: ValueType::Int,
154 label: "",
155 name: "",
156 short: "",
157 description: "",
158 formula: "",
159 calc: "",
160 direction: Direction::Neutral,
161 abbreviate: false,
162 }
163 }
164}
165
166impl SpecRow {
167 fn into_spec(self) -> AttributeSpec {
168 let opt = |s: &str| (!s.is_empty()).then(|| s.to_string());
169 AttributeSpec {
170 value_type: self.value_type,
171 label: opt(self.label),
172 name: opt(self.name),
173 short: opt(self.short),
174 description: opt(self.description),
175 formula: opt(self.formula),
176 calc: opt(self.calc),
177 direction: self.direction,
178 abbreviate: self.abbreviate.then_some(true),
179 group: opt(self.group),
180 thresholds: None,
181 }
182 }
183}
184
185pub fn attr_dict(rows: Vec<(&'static str, SpecRow)>) -> BTreeMap<String, AttributeSpec> {
187 rows.into_iter()
188 .map(|(k, r)| (k.to_string(), r.into_spec()))
189 .collect()
190}
191
192pub fn group(label: &str, description: &str) -> AttributeGroup {
194 AttributeGroup {
195 label: Some(label.to_string()),
196 description: Some(description.to_string()),
197 }
198}
199
200#[derive(Debug, Clone, Serialize, Deserialize)]
203pub struct NodeKindSpec {
204 #[serde(default, skip_serializing_if = "Option::is_none")]
205 pub label: Option<String>,
206 #[serde(default, skip_serializing_if = "Option::is_none")]
207 pub plural: Option<String>,
208 #[serde(default, skip_serializing_if = "Option::is_none")]
209 pub fill: Option<String>,
210 #[serde(default, skip_serializing_if = "Option::is_none")]
211 pub stroke: Option<String>,
212 #[serde(default, skip_serializing_if = "Option::is_none")]
215 pub external: Option<bool>,
216}
217
218#[derive(Debug, Clone, Serialize, Deserialize)]
220pub struct CycleKindSpec {
221 #[serde(default, skip_serializing_if = "Option::is_none")]
222 pub label: Option<String>,
223 #[serde(default, skip_serializing_if = "Option::is_none")]
224 pub description: Option<String>,
225}
226
227#[derive(Debug, Clone, Serialize, Deserialize)]
232pub struct Grouping {
233 #[serde(default, skip_serializing_if = "Option::is_none")]
234 pub key: Option<String>,
235 #[serde(default, skip_serializing_if = "Option::is_none")]
236 pub function: Option<String>,
237}
238
239#[derive(Debug, Clone, Serialize, Deserialize)]
243pub struct Level {
244 pub name: String,
245 pub edge_kinds: BTreeMap<String, EdgeKindSpec>,
246 pub node_attributes: BTreeMap<String, AttributeSpec>,
247 pub edge_attributes: BTreeMap<String, AttributeSpec>,
248 pub attribute_groups: BTreeMap<String, AttributeGroup>,
249 #[serde(default)]
252 pub node_kinds: BTreeMap<String, NodeKindSpec>,
253 #[serde(default)]
255 pub cycle_kinds: BTreeMap<String, CycleKindSpec>,
256 #[serde(default, skip_serializing_if = "Option::is_none")]
258 pub grouping: Option<Grouping>,
259}