1use serde::{Deserialize, Deserializer, Serialize, Serializer};
2use std::collections::BTreeMap;
3
4pub use crate::ecel::{parse_condition, Condition};
5
6pub type NodeId = String;
7pub type GateId = String;
8
9#[derive(Debug, Clone, Serialize)]
10pub struct EtlDocument {
11 pub etdl: String,
12 pub info: Info,
13 #[serde(default)]
14 pub asyncapi_imports: BTreeMap<String, String>,
15
16 #[serde(default)]
17 pub supplements: Vec<Supplement>,
18
19 #[serde(default)]
26 pub libraries: Vec<LibraryImport>,
27
28 #[serde(default)]
29 pub components: Option<Components>,
30
31 pub event_trees: BTreeMap<String, EventTree>,
32
33 #[serde(default)]
34 pub fault_trees: Option<BTreeMap<String, FaultTree>>,
35
36 pub extensions: BTreeMap<String, serde_yaml::Value>,
37}
38
39#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
41pub struct Supplement {
42 pub id: String,
43 pub version: String,
44 #[serde(default)]
45 pub required: bool,
46}
47
48#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
56pub struct LibraryImport {
57 pub name: String,
61 pub version: String,
65 #[serde(default)]
66 pub required: bool,
67}
68
69impl<'de> Deserialize<'de> for EtlDocument {
70 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
71 where
72 D: Deserializer<'de>,
73 {
74 use serde::de::{Error, MapAccess, Visitor};
75 use std::fmt;
76
77 struct DocVisitor;
78
79 impl<'de> Visitor<'de> for DocVisitor {
80 type Value = EtlDocument;
81
82 fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
83 f.write_str("an ETDL document")
84 }
85
86 fn visit_map<A>(self, mut map: A) -> Result<EtlDocument, A::Error>
87 where
88 A: MapAccess<'de>,
89 {
90 let mut etdl: Option<String> = None;
91 let mut info: Option<Info> = None;
92 let mut asyncapi_imports: BTreeMap<String, String> = BTreeMap::new();
93 let mut supplements: Vec<Supplement> = Vec::new();
94 let mut libraries: Vec<LibraryImport> = Vec::new();
95 let mut components: Option<Components> = None;
96 let mut event_trees_map: BTreeMap<String, EventTree> = BTreeMap::new();
97 let mut event_tree_legacy: Option<EventTree> = None;
98 let mut fault_trees: Option<BTreeMap<String, FaultTree>> = None;
99 let mut extensions: BTreeMap<String, serde_yaml::Value> = BTreeMap::new();
100
101 while let Some(key) = map.next_key::<String>()? {
102 match key.as_str() {
103 "etdl" => {
104 if etdl.is_some() {
105 return Err(Error::duplicate_field("etdl"));
106 }
107 etdl = Some(map.next_value()?);
108 }
109 "info" => {
110 if info.is_some() {
111 return Err(Error::duplicate_field("info"));
112 }
113 info = Some(map.next_value()?);
114 }
115 "asyncapi_imports" => {
116 asyncapi_imports = map.next_value()?;
117 }
118 "supplements" => {
119 supplements = map.next_value()?;
120 }
121 "libraries" => {
122 libraries = map.next_value()?;
123 }
124 "components" => {
125 components = map.next_value()?;
126 }
127 "eventTrees" => {
128 event_trees_map = map.next_value()?;
129 }
130 "eventTree" => {
131 event_tree_legacy = Some(map.next_value()?);
132 }
133 "faultTrees" => {
134 fault_trees = map.next_value()?;
135 }
136 k if k.starts_with("x-") => {
137 let val: serde_yaml::Value = map.next_value()?;
138 extensions.insert(k.to_string(), val);
139 }
140 unknown => {
141 return Err(Error::custom(format!(
142 "unrecognized field '{}' in ETDL document; extension fields must start with 'x-'",
143 unknown
144 )));
145 }
146 }
147 }
148
149 let etdl = etdl.ok_or_else(|| Error::missing_field("etdl"))?;
150 let info = info.ok_or_else(|| Error::missing_field("info"))?;
151
152 let event_trees = match (event_trees_map.is_empty(), event_tree_legacy) {
153 (true, Some(tree)) => {
154 let mut map = BTreeMap::new();
155 map.insert("default".to_string(), tree);
156 map
157 }
158 (true, None) => {
159 return Err(Error::custom(
160 "at least one of 'eventTrees' or 'eventTree' (deprecated) must be present",
161 ));
162 }
163 (false, None) => event_trees_map,
164 (false, Some(_)) => {
165 return Err(Error::custom(
166 "both 'eventTrees' and 'eventTree' (deprecated) provided; use only 'eventTrees'",
167 ));
168 }
169 };
170
171 Ok(EtlDocument {
172 etdl,
173 info,
174 asyncapi_imports,
175 supplements,
176 libraries,
177 components,
178 event_trees,
179 fault_trees,
180 extensions,
181 })
182 }
183 }
184
185 deserializer.deserialize_map(DocVisitor)
186 }
187}
188
189#[derive(Debug, Clone, Serialize, Deserialize)]
190pub struct Info {
191 pub title: String,
192 pub version: String,
193 #[serde(deserialize_with = "deserialize_domain")]
194 pub domain: String,
195 #[serde(default)]
196 pub description: Option<String>,
197}
198
199fn deserialize_domain<'de, D>(deserializer: D) -> Result<String, D::Error>
200where
201 D: Deserializer<'de>,
202{
203 let s = String::deserialize(deserializer)?;
204 if s.is_empty() || !s.chars().next().unwrap().is_ascii_alphabetic() {
205 return Err(serde::de::Error::custom(
206 "domain must match ^[A-Za-z][A-Za-z0-9]*$",
207 ));
208 }
209 if s.chars().any(|c| !c.is_ascii_alphanumeric()) {
210 return Err(serde::de::Error::custom(
211 "domain must match ^[A-Za-z][A-Za-z0-9]*$",
212 ));
213 }
214 Ok(s)
215}
216
217#[derive(Debug, Clone, Serialize, Deserialize, Default)]
218pub struct Components {
219 #[serde(default)]
220 pub barriers: Option<BTreeMap<String, Barrier>>,
221 #[serde(default)]
222 pub operations: Option<BTreeMap<String, Operation>>,
223 #[serde(default)]
224 pub gates: Option<BTreeMap<String, Gate>>,
225 #[serde(default)]
226 pub basic_events: Option<BTreeMap<String, BasicEvent>>,
227 #[serde(default)]
230 pub messages: Option<BTreeMap<String, Message>>,
231}
232
233#[derive(Debug, Clone, Serialize, Deserialize)]
238pub struct Message {
239 #[serde(default)]
240 pub name: Option<String>,
241 pub payload: serde_yaml::Value,
243 #[serde(default)]
245 pub headers: Option<serde_yaml::Value>,
246}
247
248#[derive(Debug, Clone, Serialize, Deserialize)]
259pub struct LibraryDocument {
260 pub etdl: String,
263 pub library: LibraryInfo,
264 #[serde(default)]
265 pub components: Components,
266}
267
268#[derive(Debug, Clone, Serialize, Deserialize)]
272pub struct LibraryInfo {
273 pub name: String,
276 pub version: String,
278 #[serde(default)]
279 pub description: Option<String>,
280 #[serde(default, rename = "dependsOn")]
284 pub depends_on: Vec<LibraryImport>,
285}
286
287#[derive(Debug, Clone, Serialize, Deserialize)]
288pub struct EventTree {
289 #[serde(rename = "initiatingEvent")]
290 pub initiating_event: InitiatingEvent,
291 pub nodes: BTreeMap<NodeId, Node>,
292 #[serde(default)]
293 pub description: Option<String>,
294}
295
296#[derive(Debug, Clone, Serialize, Deserialize)]
297pub struct InitiatingEvent {
298 pub id: String,
299 pub message: MessageRef,
300 pub next: NodeId,
301}
302
303#[derive(Debug, Clone, Serialize, Deserialize)]
304#[serde(tag = "type")]
305pub enum Node {
306 #[serde(rename = "barrier")]
307 Barrier(Barrier),
308 #[serde(rename = "operation")]
309 Operation(Operation),
310 #[serde(rename = "consequence")]
311 Consequence(Consequence),
312}
313
314impl Node {
315 pub fn node_type(&self) -> &str {
316 match self {
317 Node::Barrier(_) => "barrier",
318 Node::Operation(_) => "operation",
319 Node::Consequence(_) => "consequence",
320 }
321 }
322}
323
324#[derive(Debug, Clone, Serialize, Deserialize)]
325pub struct Barrier {
326 pub branches: Vec<Branch>,
327 #[serde(default)]
328 pub description: Option<String>,
329}
330
331#[derive(Debug, Clone, Serialize, Deserialize)]
332pub struct Branch {
333 pub outcome: String,
334
335 #[serde(deserialize_with = "deserialize_condition")]
336 pub condition: Condition,
337
338 #[serde(default, deserialize_with = "deserialize_optional_f64")]
339 pub probability: Option<f64>,
340
341 #[serde(default, alias = "probabilityOfSuccess")]
342 pub probability_of_success: Option<f64>,
343
344 #[serde(default, alias = "probabilityOfFailure")]
345 pub probability_of_failure: Option<f64>,
346
347 #[serde(default, alias = "probabilitySource")]
348 pub probability_source: Option<InternalRef>,
349
350 pub next: NodeId,
351}
352
353fn deserialize_condition<'de, D>(deserializer: D) -> Result<Condition, D::Error>
354where
355 D: Deserializer<'de>,
356{
357 let s = String::deserialize(deserializer)?;
358 parse_condition(&s).map_err(serde::de::Error::custom)
359}
360
361fn deserialize_optional_f64<'de, D>(deserializer: D) -> Result<Option<f64>, D::Error>
362where
363 D: Deserializer<'de>,
364{
365 Option::<f64>::deserialize(deserializer)
366}
367
368impl Branch {
369 pub fn effective_probability(&self) -> Option<f64> {
370 self.probability
371 .or(self.probability_of_success)
372 .or(self.probability_of_failure)
373 }
374
375 pub fn has_probability_source(&self) -> bool {
376 self.probability_source.is_some()
377 || self.probability.is_some()
378 || self.probability_of_success.is_some()
379 || self.probability_of_failure.is_some()
380 }
381}
382
383#[derive(Debug, Clone, Serialize, Deserialize)]
384pub struct Operation {
385 #[serde(default = "default_action")]
386 pub action: ActionKind,
387 pub handler: String,
388
389 #[serde(default)]
390 pub emits: Option<MessageRef>,
391
392 pub next: NodeId,
393
394 #[serde(default, alias = "onFailure")]
395 pub on_failure: Option<NodeId>,
396
397 #[serde(default, alias = "onFailureProbabilitySource")]
398 pub on_failure_probability_source: Option<InternalRef>,
399
400 #[serde(default, alias = "retryPolicy")]
401 pub retry_policy: Option<RetryPolicy>,
402
403 #[serde(default, alias = "timeoutMs")]
404 pub timeout_ms: Option<u64>,
405
406 #[serde(default)]
407 pub description: Option<String>,
408}
409
410fn default_action() -> ActionKind {
411 ActionKind::Execute
412}
413
414#[derive(Debug, Clone, Serialize, Deserialize)]
415pub enum ActionKind {
416 #[serde(rename = "execute")]
417 Execute,
418}
419
420#[derive(Debug, Clone, Serialize, Deserialize)]
421pub struct RetryPolicy {
422 #[serde(default = "default_max_attempts", alias = "maxAttempts")]
423 pub max_attempts: u32,
424
425 #[serde(default = "default_backoff_ms", alias = "backoffMs")]
426 pub backoff_ms: u64,
427
428 #[serde(default, alias = "backoffStrategy")]
429 pub backoff_strategy: Option<BackoffStrategy>,
430}
431
432fn default_max_attempts() -> u32 {
433 1
434}
435fn default_backoff_ms() -> u64 {
436 0
437}
438
439#[derive(Debug, Clone, Serialize, Deserialize, Default)]
440pub enum BackoffStrategy {
441 #[serde(rename = "fixed")]
442 #[default]
443 Fixed,
444 #[serde(rename = "exponential")]
445 Exponential,
446}
447
448#[derive(Debug, Clone, Serialize, Deserialize)]
449pub struct Consequence {
450 #[serde(rename = "operation")]
451 pub consequence_operation: ConsequenceOperation,
452 #[serde(default)]
453 pub channel: Option<ChannelRef>,
454 #[serde(default)]
455 pub message: Option<MessageRef>,
456 #[serde(default)]
457 pub description: Option<String>,
458}
459
460#[derive(Debug, Clone, Serialize, Deserialize)]
461pub enum ConsequenceOperation {
462 #[serde(rename = "send")]
463 Send,
464 #[serde(rename = "terminate")]
465 Terminate,
466}
467
468#[derive(Debug, Clone, Serialize, Deserialize)]
469pub struct FaultTree {
470 #[serde(rename = "topEvent")]
471 pub top_event: TopEvent,
472
473 #[serde(default)]
474 pub gates: Option<BTreeMap<GateId, Gate>>,
475
476 #[serde(rename = "basicEvents")]
477 pub basic_events: BTreeMap<String, BasicEvent>,
478
479 #[serde(default)]
480 pub transfers: Option<BTreeMap<String, TransferNode>>,
481
482 #[serde(default)]
483 pub description: Option<String>,
484}
485
486#[derive(Debug, Clone, Serialize, Deserialize)]
487pub struct TopEvent {
488 pub id: String,
489 pub description: String,
490 #[serde(default)]
491 pub message: Option<MessageRef>,
492 #[serde(rename = "rootCause")]
493 pub root_cause: FaultTreeNodeRef,
494}
495
496#[derive(Debug, Clone, Serialize, Deserialize)]
497pub struct Gate {
498 #[serde(rename = "type")]
499 pub gate_type: GateType,
500 pub inputs: Vec<FaultTreeNodeRef>,
501 #[serde(default)]
502 pub k: Option<u32>,
503 #[serde(default)]
504 pub description: Option<String>,
505 #[serde(default, alias = "inhibitCondition")]
506 pub inhibit_condition: Option<String>,
507}
508
509#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
510pub enum GateType {
511 #[serde(rename = "AND")]
512 And,
513 #[serde(rename = "OR")]
514 Or,
515 #[serde(rename = "NOT")]
516 Not,
517 #[serde(rename = "XOR")]
518 Xor,
519 #[serde(rename = "VOTING")]
520 Voting,
521 #[serde(rename = "INHIBIT")]
522 Inhibit,
523 #[serde(rename = "PRIORITY_AND")]
524 PriorityAnd,
525}
526
527#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
528pub enum BasicEventType {
529 #[serde(rename = "basic")]
530 Basic,
531 #[serde(rename = "house")]
532 House,
533 #[serde(rename = "undeveloped")]
534 Undeveloped,
535 #[serde(rename = "conditional")]
536 Conditional,
537}
538
539#[derive(Debug, Clone, Serialize, Deserialize)]
540pub struct TransferNode {
541 pub target: String,
542 #[serde(default)]
543 pub label: Option<String>,
544}
545
546#[derive(Debug, Clone, Serialize, Deserialize)]
547pub struct BasicEvent {
548 pub description: String,
549 #[serde(default)]
550 pub probability: Option<f64>,
551 #[serde(default, alias = "failureRate")]
552 pub failure_rate: Option<f64>,
553 #[serde(default, alias = "missionTime")]
554 pub mission_time: Option<f64>,
555 #[serde(default)]
556 pub undeveloped: Option<bool>,
557 #[serde(default, alias = "eventType")]
558 pub event_type: Option<BasicEventType>,
559 #[serde(default)]
560 pub message: Option<MessageRef>,
561 #[serde(default, flatten)]
563 pub extensions: BTreeMap<String, serde_yaml::Value>,
564}
565
566#[derive(Debug, Clone)]
567pub struct ExternalRef {
568 pub alias: String,
569 pub pointer: String,
570}
571
572#[derive(Debug, Clone)]
573pub struct InternalRef {
574 pub pointer: String,
575}
576
577pub type FaultTreeNodeRef = String;
578
579impl Serialize for ExternalRef {
580 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
581 where
582 S: Serializer,
583 {
584 serializer.serialize_str(&format!("{}#{}", self.alias, self.pointer))
585 }
586}
587
588impl<'de> Deserialize<'de> for ExternalRef {
589 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
590 where
591 D: Deserializer<'de>,
592 {
593 let s = String::deserialize(deserializer)?;
594 parse_external_ref(&s).map_err(serde::de::Error::custom)
595 }
596}
597
598fn parse_external_ref(s: &str) -> Result<ExternalRef, String> {
599 if let Some(hash_pos) = s.find('#') {
600 let alias = &s[..hash_pos];
601 let pointer = &s[hash_pos..];
602 if alias.is_empty() {
603 if pointer.starts_with("#/") {
604 return Err(format!(
605 "bare JSON Pointer '{}' without import alias; use InternalRef for same-document references",
606 pointer
607 ));
608 }
609 return Err("empty alias in external reference".to_string());
610 }
611 if alias
612 .chars()
613 .any(|c| !c.is_ascii_alphanumeric() && c != '_')
614 {
615 return Err(format!("invalid import alias '{}'", alias));
616 }
617 Ok(ExternalRef {
618 alias: alias.to_string(),
619 pointer: pointer.to_string(),
620 })
621 } else {
622 Err(format!("no '#' found in external reference '{}'", s))
623 }
624}
625
626impl Serialize for InternalRef {
627 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
628 where
629 S: Serializer,
630 {
631 serializer.serialize_str(&self.pointer)
632 }
633}
634
635impl<'de> Deserialize<'de> for InternalRef {
636 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
637 where
638 D: Deserializer<'de>,
639 {
640 let s = String::deserialize(deserializer)?;
641 if !s.starts_with("#/") {
642 return Err(serde::de::Error::custom(format!(
643 "invalid internal reference '{}'; must start with '#/'",
644 s
645 )));
646 }
647 Ok(InternalRef { pointer: s })
648 }
649}
650
651impl ExternalRef {
652 pub fn as_string(&self) -> String {
653 format!("{}#{}", self.alias, self.pointer)
654 }
655}
656
657impl InternalRef {
658 pub fn as_string(&self) -> String {
659 self.pointer.clone()
660 }
661}
662
663#[derive(Debug, Clone)]
664pub enum ParsedReference {
665 External(ExternalRef),
666 Internal(InternalRef),
667}
668
669pub fn parse_reference(s: &str) -> Result<ParsedReference, String> {
670 if s.starts_with("#/") {
671 Ok(ParsedReference::Internal(InternalRef {
672 pointer: s.to_string(),
673 }))
674 } else if let Some(hash_pos) = s.find('#') {
675 let alias = &s[..hash_pos];
676 let pointer = &s[hash_pos..];
677 if alias.is_empty() || pointer.is_empty() || !pointer.starts_with('#') {
678 return Err(format!("invalid reference syntax: '{}'", s));
679 }
680 parse_external_ref(s).map(ParsedReference::External)
681 } else {
682 Err(format!(
683 "reference '{}' matches neither external nor internal format",
684 s
685 ))
686 }
687}
688
689#[derive(Debug, Clone)]
694pub enum MessageRef {
695 External(ExternalRef),
696 Internal(InternalRef),
697}
698
699impl Serialize for MessageRef {
700 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
701 where
702 S: Serializer,
703 {
704 match self {
705 MessageRef::External(r) => r.serialize(serializer),
706 MessageRef::Internal(r) => r.serialize(serializer),
707 }
708 }
709}
710
711impl<'de> Deserialize<'de> for MessageRef {
712 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
713 where
714 D: Deserializer<'de>,
715 {
716 let s = String::deserialize(deserializer)?;
717 match parse_reference(&s).map_err(serde::de::Error::custom)? {
718 ParsedReference::External(r) => Ok(MessageRef::External(r)),
719 ParsedReference::Internal(r) => Ok(MessageRef::Internal(r)),
720 }
721 }
722}
723
724impl MessageRef {
725 pub fn as_string(&self) -> String {
726 match self {
727 MessageRef::External(r) => r.as_string(),
728 MessageRef::Internal(r) => r.as_string(),
729 }
730 }
731}
732
733#[derive(Debug, Clone)]
739pub enum ChannelRef {
740 External(ExternalRef),
741 Bare(String),
742}
743
744impl Serialize for ChannelRef {
745 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
746 where
747 S: Serializer,
748 {
749 match self {
750 ChannelRef::External(r) => r.serialize(serializer),
751 ChannelRef::Bare(s) => serializer.serialize_str(s),
752 }
753 }
754}
755
756impl<'de> Deserialize<'de> for ChannelRef {
757 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
758 where
759 D: Deserializer<'de>,
760 {
761 let s = String::deserialize(deserializer)?;
762 if s.starts_with("#/") {
763 return Err(serde::de::Error::custom(format!(
764 "'{}' is not a valid Channel Reference: internal pointers are not supported for channel (Section 5.3.5)",
765 s
766 )));
767 }
768 if s.contains('#') {
769 parse_external_ref(&s)
770 .map(ChannelRef::External)
771 .map_err(serde::de::Error::custom)
772 } else {
773 Ok(ChannelRef::Bare(s))
774 }
775 }
776}
777
778impl ChannelRef {
779 pub fn as_string(&self) -> String {
780 match self {
781 ChannelRef::External(r) => r.as_string(),
782 ChannelRef::Bare(s) => s.clone(),
783 }
784 }
785}