rafia_stpa/stpa/
control_loop_sequences.rs1use std::{
17 ffi::OsStr,
18 fs::{self, File},
19 path::Path,
20};
21
22use csv;
23use serde::{Deserialize, Serialize};
24use serde_yaml;
25
26use crate::stpa::{InteractionLink, LoadError, CYCLE_DETECTED_MSG};
27
28use super::{ControlLoopLink, StpaData};
29
30#[derive(Debug, Serialize, Deserialize)]
32pub struct ControlLoopSequenceCsv {
33 #[serde(rename = "CL-Sequence Id")]
34 pub id: String,
35 #[serde(rename = "Loop")]
36 pub control_loop: String,
37 #[serde(rename = "Step")]
38 pub step: String,
39 #[serde(rename = "Interaction Id")]
40 pub interaction: String,
41 #[serde(rename = "Provider process model")]
42 pub provider_process_model: String,
43 #[serde(rename = "Provider logic")]
44 pub provider_logic: String,
45 #[serde(rename = "Expected Receiver behaviour")]
46 pub expected_target_behaviour: String,
47}
48
49#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
50pub struct ControlLoopSequence {
51 pub id: String,
52 pub control_loop: ControlLoopLink,
53 pub step: String,
54 pub interaction: InteractionLink,
55 pub provider_process_model: String,
56 pub provider_logic: String,
57 pub expected_target_behaviour: String,
58}
59
60impl ControlLoopSequence {
61 pub fn describe(
62 &self,
63 stpa_data: &StpaData,
64 currently_expanding: &mut Vec<String>,
65 ) -> Result<serde_json::Value, String> {
66 if currently_expanding.contains(&self.id) {
68 return Ok(serde_json::json!({
69 "control_loop_sequence": {
70 "id": self.id,
71 "message": CYCLE_DETECTED_MSG,
72 }
73 }));
74 } else {
75 currently_expanding.push(self.id.clone());
76 }
77
78 let mut obj = serde_json::json!({"control_loop_sequence": self});
79 obj["control_loop_sequence"]["control_loop"] =
80 stpa_data.describe_inner(self.control_loop.0.as_ref(), currently_expanding)?;
81 obj["control_loop_sequence"]["interaction"] =
82 stpa_data.describe_inner(self.interaction.0.as_ref(), currently_expanding)?;
83
84 let popped = currently_expanding.pop();
85 debug_assert_eq!(popped.as_ref(), Some(&self.id));
86 Ok(obj)
87 }
88}
89
90impl From<ControlLoopSequenceCsv> for ControlLoopSequence {
91 fn from(csv: ControlLoopSequenceCsv) -> Self {
92 ControlLoopSequence {
93 id: csv.id,
94 control_loop: ControlLoopLink::from(csv.control_loop),
95 step: csv.step,
96 interaction: InteractionLink::from(csv.interaction),
97 provider_process_model: csv.provider_process_model,
98 provider_logic: csv.provider_logic,
99 expected_target_behaviour: csv.expected_target_behaviour,
100 }
101 }
102}
103
104#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Default)]
105pub struct ControlLoopSequences {
106 pub control_loop_sequences: Vec<ControlLoopSequence>,
107}
108
109impl ControlLoopSequences {
110 pub fn from_yaml(text: &str) -> Result<ControlLoopSequences, LoadError> {
111 serde_yaml::from_str::<ControlLoopSequences>(text).map_err(LoadError::Yaml)
112 }
113 pub fn to_yaml(&self) -> Result<String, LoadError> {
114 serde_yaml::to_string(&self).map_err(LoadError::Yaml)
115 }
116 pub fn from_csv(filename: &Path) -> Result<ControlLoopSequences, LoadError> {
117 let mut control_loop_sequences = vec![];
118 let file = File::open(filename)?;
119 for control_loop_sequence in csv::ReaderBuilder::new()
120 .from_reader(file)
121 .deserialize::<ControlLoopSequenceCsv>()
122 {
123 control_loop_sequences.push(control_loop_sequence?.into());
124 }
125 Ok(ControlLoopSequences {
126 control_loop_sequences,
127 })
128 }
129 pub fn from_file(filename: &Path) -> Result<ControlLoopSequences, LoadError> {
130 match filename.extension().and_then(OsStr::to_str) {
131 Some("csv") => Self::from_csv(filename),
132 Some("yml") | Some("yaml") => {
133 let text = fs::read_to_string(filename)?;
134 Self::from_yaml(&text)
135 }
136 _ => Err(LoadError::NotSupportedFormat),
137 }
138 }
139 pub fn find(&self, id: &str) -> Option<&ControlLoopSequence> {
140 self.control_loop_sequences
141 .iter()
142 .find(|control_loop_sequence| control_loop_sequence.id == id)
143 }
144}