1use 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, UcaContextLink, UcaType, CYCLE_DETECTED_MSG};
27
28use super::{ConstraintLink, StpaData};
29
30#[derive(Debug, Serialize, Deserialize)]
32pub struct UcaCsv {
33 #[serde(rename = "UCA Id")]
34 pub id: String,
35 #[serde(rename = "UCA Definition")]
36 pub uca_definition: String,
37 #[serde(rename = "CA")]
38 pub ca: String,
39 #[serde(rename = "UCAType")]
40 pub uca_type: String,
41 #[serde(rename = "UCA Context")]
42 pub uca_context: String,
43 #[serde(rename = "Constraint Id")]
44 pub constraint_id: String,
45 }
48
49#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
50pub struct Uca {
51 pub id: String,
52 pub uca_definition: String,
53 pub ca: InteractionLink,
54 pub uca_type: UcaType,
55 pub uca_context: UcaContextLink,
56 pub constraint_id: Vec<ConstraintLink>,
57}
58
59impl Uca {
60 pub fn describe(
61 &self,
62 stpa_data: &StpaData,
63 currently_expanding: &mut Vec<String>,
64 ) -> Result<serde_json::Value, String> {
65 if currently_expanding.contains(&self.id) {
67 return Ok(serde_json::json!({
68 "uca": {
69 "id": self.id,
70 "message": CYCLE_DETECTED_MSG,
71 }
72 }));
73 } else {
74 currently_expanding.push(self.id.clone());
75 }
76
77 let mut obj = serde_json::json!({"uca": self});
78
79 obj["uca"]["ca"] = stpa_data.describe_inner(self.ca.0.as_ref(), currently_expanding)?;
80 obj["uca"]["uca_context"] =
81 stpa_data.describe_inner(self.uca_context.0.as_ref(), currently_expanding)?;
82
83 let ucas = self
84 .constraint_id
85 .iter()
86 .map(|constraint| {
87 stpa_data
88 .describe_inner(constraint.0.as_ref(), currently_expanding)
89 .map_err(|e| {
90 format!(
91 "Failed while resolving constraint id {constraint} of uca {}: {e}",
92 self.id
93 )
94 })
95 })
96 .collect::<Result<Vec<_>, _>>()?;
97
98 obj["uca"]["constraint_id"] = serde_json::json!(ucas);
99
100 let popped = currently_expanding.pop();
101 debug_assert_eq!(popped.as_ref(), Some(&self.id));
102 Ok(obj)
103 }
104}
105
106impl From<UcaCsv> for Uca {
107 fn from(csv: UcaCsv) -> Self {
108 Uca {
109 id: csv.id,
110 uca_definition: csv.uca_definition,
111 ca: InteractionLink::from(csv.ca),
112 uca_type: UcaType::from(csv.uca_type.as_str()),
113 uca_context: UcaContextLink::from(csv.uca_context),
114 constraint_id: csv
115 .constraint_id
116 .split(",")
117 .filter(|e| !e.is_empty())
118 .map(|s| ConstraintLink::from(s.trim().to_string()))
119 .collect(),
120 }
121 }
122}
123
124#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Default)]
125pub struct Ucas {
126 pub ucas: Vec<Uca>,
127}
128
129impl Ucas {
130 pub fn from_yaml(text: &str) -> Result<Ucas, LoadError> {
131 serde_yaml::from_str::<Ucas>(text).map_err(LoadError::Yaml)
132 }
133 pub fn to_yaml(&self) -> Result<String, LoadError> {
134 serde_yaml::to_string(&self).map_err(LoadError::Yaml)
135 }
136 pub fn from_csv(filename: &Path) -> Result<Ucas, LoadError> {
137 let mut ucas = vec![];
138 let file = File::open(filename)?;
139 for uca in csv::ReaderBuilder::new()
140 .from_reader(file)
141 .deserialize::<UcaCsv>()
142 {
143 ucas.push(uca?.into());
144 }
145 Ok(Ucas { ucas })
146 }
147 pub fn from_file(filename: &Path) -> Result<Ucas, LoadError> {
148 match filename.extension().and_then(OsStr::to_str) {
149 Some("csv") => Self::from_csv(filename),
150 Some("yml") | Some("yaml") => {
151 let text = fs::read_to_string(filename)?;
152 Self::from_yaml(&text)
153 }
154 _ => Err(LoadError::NotSupportedFormat),
155 }
156 }
157 pub fn find(&self, id: &str) -> Option<&Uca> {
158 self.ucas.iter().find(|uca| uca.id == id)
159 }
160}