1use serde::{Deserialize, Serialize};
13use std::collections::HashMap;
14
15use crate::graph::StaticGraph;
16
17#[derive(Clone, Debug, Serialize, Deserialize, Default)]
25pub struct StaticDomainValue {
26 pub id: String,
28 #[serde(default)]
30 pub selected: bool,
31 #[serde(default)]
33 pub text: HashMap<String, String>,
34}
35
36impl StaticDomainValue {
37 pub fn new(id: String, selected: bool, text: HashMap<String, String>) -> Self {
39 Self { id, selected, text }
40 }
41
42 pub fn lang(&self, language: &str) -> Option<&str> {
44 self.text
45 .get(language)
46 .or_else(|| self.text.get("en"))
47 .or_else(|| self.text.values().next())
48 .map(|s| s.as_str())
49 }
50
51 pub fn display(&self) -> &str {
53 self.lang("en").unwrap_or_default()
54 }
55}
56
57#[derive(Clone, Debug, Serialize, Deserialize, Default)]
63pub struct NodeConfigBoolean {
64 #[serde(default, rename = "trueLabel")]
66 pub true_label: HashMap<String, String>,
67 #[serde(default, rename = "falseLabel")]
69 pub false_label: HashMap<String, String>,
70 #[serde(default)]
72 pub i18n_properties: Vec<String>,
73}
74
75impl NodeConfigBoolean {
76 pub fn new(true_label: HashMap<String, String>, false_label: HashMap<String, String>) -> Self {
78 Self {
79 true_label,
80 false_label,
81 i18n_properties: vec![],
82 }
83 }
84
85 pub fn get_label(&self, value: bool, language: &str) -> Option<&str> {
87 let labels = if value {
88 &self.true_label
89 } else {
90 &self.false_label
91 };
92 labels
93 .get(language)
94 .or_else(|| labels.get("en"))
95 .or_else(|| labels.values().next())
96 .map(|s| s.as_str())
97 }
98}
99
100#[derive(Clone, Debug, Serialize, Deserialize, Default)]
102pub struct NodeConfigConcept {
103 #[serde(default, rename = "rdmCollection")]
105 pub rdm_collection: String,
106}
107
108impl NodeConfigConcept {
109 pub fn new(rdm_collection: String) -> Self {
111 Self { rdm_collection }
112 }
113}
114
115#[derive(Clone, Debug, Serialize, Deserialize, Default)]
117pub struct NodeConfigReference {
118 #[serde(default, rename = "controlledList")]
120 pub controlled_list: String,
121 #[serde(default, rename = "rdmCollection")]
123 pub rdm_collection: String,
124 #[serde(default, rename = "multiValue")]
126 pub multi_value: bool,
127}
128
129impl NodeConfigReference {
130 pub fn new(controlled_list: String, rdm_collection: String, multi_value: bool) -> Self {
132 Self {
133 controlled_list,
134 rdm_collection,
135 multi_value,
136 }
137 }
138
139 pub fn get_collection_id(&self) -> Option<&str> {
141 if !self.controlled_list.is_empty() {
142 Some(&self.controlled_list)
143 } else if !self.rdm_collection.is_empty() {
144 Some(&self.rdm_collection)
145 } else {
146 None
147 }
148 }
149}
150
151#[derive(Clone, Debug, Serialize, Deserialize, Default)]
153pub struct NodeConfigDomain {
154 #[serde(default)]
156 pub options: Vec<StaticDomainValue>,
157 #[serde(default)]
159 pub i18n_config: HashMap<String, String>,
160}
161
162impl NodeConfigDomain {
163 pub fn new(options: Vec<StaticDomainValue>) -> Self {
165 Self {
166 options,
167 i18n_config: HashMap::new(),
168 }
169 }
170
171 pub fn get_selected(&self) -> Option<&StaticDomainValue> {
173 self.options.iter().find(|opt| opt.selected)
174 }
175
176 pub fn value_from_id(&self, id: &str) -> Option<&StaticDomainValue> {
178 self.options.iter().find(|opt| opt.id == id)
179 }
180
181 pub fn get_option_ids(&self) -> Vec<&str> {
183 self.options.iter().map(|opt| opt.id.as_str()).collect()
184 }
185}
186
187#[derive(Clone, Debug)]
193pub enum NodeConfig {
194 Boolean(NodeConfigBoolean),
195 Concept(NodeConfigConcept),
196 Reference(NodeConfigReference),
197 Domain(NodeConfigDomain),
198}
199
200impl NodeConfig {
201 pub fn as_boolean(&self) -> Option<&NodeConfigBoolean> {
203 match self {
204 NodeConfig::Boolean(c) => Some(c),
205 _ => None,
206 }
207 }
208
209 pub fn as_concept(&self) -> Option<&NodeConfigConcept> {
211 match self {
212 NodeConfig::Concept(c) => Some(c),
213 _ => None,
214 }
215 }
216
217 pub fn as_reference(&self) -> Option<&NodeConfigReference> {
219 match self {
220 NodeConfig::Reference(c) => Some(c),
221 _ => None,
222 }
223 }
224
225 pub fn as_domain(&self) -> Option<&NodeConfigDomain> {
227 match self {
228 NodeConfig::Domain(c) => Some(c),
229 _ => None,
230 }
231 }
232
233 pub fn type_name(&self) -> &'static str {
235 match self {
236 NodeConfig::Boolean(_) => "boolean",
237 NodeConfig::Concept(_) => "concept",
238 NodeConfig::Reference(_) => "reference",
239 NodeConfig::Domain(_) => "domain-value",
240 }
241 }
242}
243
244#[derive(Debug, Default)]
252pub struct NodeConfigManager {
253 configs: HashMap<String, NodeConfig>,
255}
256
257impl NodeConfigManager {
258 pub fn new() -> Self {
260 Self {
261 configs: HashMap::new(),
262 }
263 }
264
265 pub fn build_from_graph(&mut self, graph: &StaticGraph) {
267 for node in graph.nodes.iter() {
268 if let Some(config) = self.build_config_for_node(&node.datatype, &node.config) {
269 self.configs.insert(node.nodeid.clone(), config);
270 }
271 }
272 }
273
274 pub fn from_graph_json(&mut self, graph_json: &str) -> Result<(), String> {
276 let graph: StaticGraph = serde_json::from_str(graph_json)
277 .map_err(|e| format!("Failed to parse graph: {}", e))?;
278
279 self.build_from_graph(&graph);
280 Ok(())
281 }
282
283 pub fn get_boolean(&self, nodeid: &str) -> Option<&NodeConfigBoolean> {
285 self.configs.get(nodeid).and_then(|c| c.as_boolean())
286 }
287
288 pub fn get_concept(&self, nodeid: &str) -> Option<&NodeConfigConcept> {
290 self.configs.get(nodeid).and_then(|c| c.as_concept())
291 }
292
293 pub fn get_reference(&self, nodeid: &str) -> Option<&NodeConfigReference> {
295 self.configs.get(nodeid).and_then(|c| c.as_reference())
296 }
297
298 pub fn get_domain(&self, nodeid: &str) -> Option<&NodeConfigDomain> {
300 self.configs.get(nodeid).and_then(|c| c.as_domain())
301 }
302
303 pub fn lookup_domain_value(&self, nodeid: &str, value_id: &str) -> Option<&StaticDomainValue> {
305 self.configs
306 .get(nodeid)
307 .and_then(|c| c.as_domain())
308 .and_then(|d| d.value_from_id(value_id))
309 }
310
311 pub fn has_config(&self, nodeid: &str) -> bool {
313 self.configs.contains_key(nodeid)
314 }
315
316 pub fn get_config_type(&self, nodeid: &str) -> Option<&'static str> {
318 self.configs.get(nodeid).map(|c| c.type_name())
319 }
320
321 pub fn get(&self, nodeid: &str) -> Option<&NodeConfig> {
323 self.configs.get(nodeid)
324 }
325
326 pub fn clear(&mut self) {
328 self.configs.clear();
329 }
330
331 pub fn len(&self) -> usize {
333 self.configs.len()
334 }
335
336 pub fn is_empty(&self) -> bool {
338 self.configs.is_empty()
339 }
340
341 fn build_config_for_node(
343 &self,
344 datatype: &str,
345 config: &HashMap<String, serde_json::Value>,
346 ) -> Option<NodeConfig> {
347 match datatype {
348 "boolean" => {
349 let cfg = self.parse_boolean_config(config);
350 Some(NodeConfig::Boolean(cfg))
351 }
352 "domain-value" | "domain-value-list" => {
353 let cfg = self.parse_domain_config(config);
354 Some(NodeConfig::Domain(cfg))
355 }
356 "concept" | "concept-list" => {
357 let cfg = self.parse_concept_config(config);
358 Some(NodeConfig::Concept(cfg))
359 }
360 "reference" => {
361 let cfg = self.parse_reference_config(config);
362 Some(NodeConfig::Reference(cfg))
363 }
364 _ => None,
365 }
366 }
367
368 fn parse_boolean_config(
370 &self,
371 config: &HashMap<String, serde_json::Value>,
372 ) -> NodeConfigBoolean {
373 let true_label = config
374 .get("trueLabel")
375 .and_then(|v| serde_json::from_value(v.clone()).ok())
376 .unwrap_or_default();
377
378 let false_label = config
379 .get("falseLabel")
380 .and_then(|v| serde_json::from_value(v.clone()).ok())
381 .unwrap_or_default();
382
383 let i18n_properties = config
384 .get("i18n_properties")
385 .and_then(|v| serde_json::from_value(v.clone()).ok())
386 .unwrap_or_default();
387
388 NodeConfigBoolean {
389 true_label,
390 false_label,
391 i18n_properties,
392 }
393 }
394
395 fn parse_domain_config(&self, config: &HashMap<String, serde_json::Value>) -> NodeConfigDomain {
397 let options: Vec<StaticDomainValue> = config
398 .get("options")
399 .and_then(|v| serde_json::from_value(v.clone()).ok())
400 .unwrap_or_default();
401
402 let i18n_config = config
403 .get("i18n_config")
404 .and_then(|v| serde_json::from_value(v.clone()).ok())
405 .unwrap_or_default();
406
407 NodeConfigDomain {
408 options,
409 i18n_config,
410 }
411 }
412
413 fn parse_concept_config(
415 &self,
416 config: &HashMap<String, serde_json::Value>,
417 ) -> NodeConfigConcept {
418 let rdm_collection = config
419 .get("rdmCollection")
420 .and_then(|v| v.as_str())
421 .unwrap_or("")
422 .to_string();
423
424 NodeConfigConcept { rdm_collection }
425 }
426
427 fn parse_reference_config(
429 &self,
430 config: &HashMap<String, serde_json::Value>,
431 ) -> NodeConfigReference {
432 let controlled_list = config
433 .get("controlledList")
434 .and_then(|v| v.as_str())
435 .unwrap_or("")
436 .to_string();
437
438 let rdm_collection = config
439 .get("rdmCollection")
440 .and_then(|v| v.as_str())
441 .unwrap_or("")
442 .to_string();
443
444 let multi_value = config
445 .get("multiValue")
446 .and_then(|v| v.as_bool())
447 .unwrap_or(false);
448
449 NodeConfigReference {
450 controlled_list,
451 rdm_collection,
452 multi_value,
453 }
454 }
455}
456
457#[cfg(test)]
458mod tests {
459 use super::*;
460
461 #[test]
462 fn test_static_domain_value() {
463 let mut text = HashMap::new();
464 text.insert("en".to_string(), "English".to_string());
465 text.insert("es".to_string(), "Español".to_string());
466
467 let dv = StaticDomainValue::new("test-id".to_string(), true, text);
468
469 assert_eq!(dv.id, "test-id");
470 assert!(dv.selected);
471 assert_eq!(dv.lang("en"), Some("English"));
472 assert_eq!(dv.lang("es"), Some("Español"));
473 assert_eq!(dv.lang("de"), Some("English")); }
475
476 #[test]
477 fn test_node_config_boolean() {
478 let mut true_label = HashMap::new();
479 true_label.insert("en".to_string(), "Yes".to_string());
480
481 let mut false_label = HashMap::new();
482 false_label.insert("en".to_string(), "No".to_string());
483
484 let config = NodeConfigBoolean::new(true_label, false_label);
485
486 assert_eq!(config.get_label(true, "en"), Some("Yes"));
487 assert_eq!(config.get_label(false, "en"), Some("No"));
488 }
489
490 #[test]
491 fn test_node_config_reference() {
492 let config1 = NodeConfigReference::new("clm-id".to_string(), "".to_string(), false);
493 assert_eq!(config1.get_collection_id(), Some("clm-id"));
494
495 let config2 = NodeConfigReference::new("".to_string(), "rdm-id".to_string(), true);
496 assert_eq!(config2.get_collection_id(), Some("rdm-id"));
497 }
498
499 #[test]
500 fn test_node_config_domain() {
501 let opt1 = StaticDomainValue::new("opt-1".to_string(), false, HashMap::new());
502 let opt2 = StaticDomainValue::new("opt-2".to_string(), true, HashMap::new());
503
504 let config = NodeConfigDomain::new(vec![opt1, opt2]);
505
506 assert_eq!(config.options.len(), 2);
507 assert_eq!(config.get_selected().map(|o| o.id.as_str()), Some("opt-2"));
508 assert!(config.value_from_id("opt-1").is_some());
509 }
510}