1use std::str::FromStr;
4
5use serde::ser::Serializer;
6use serde::{Deserialize, Serialize};
7
8use crate::crypto::KeyId;
9use crate::{supply_chain_item_derive, Error, Result};
10
11use super::rule::ArtifactRule;
12use super::supply_chain_item::SupplyChainItem;
13
14#[derive(
16 Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Default, Deserialize,
17)]
18pub struct Command(Vec<String>);
19
20impl Command {
21 pub fn is_empty(&self) -> bool {
22 self.0.is_empty()
23 }
24}
25
26impl AsRef<[String]> for Command {
27 fn as_ref(&self) -> &[String] {
28 &self.0
29 }
30}
31
32impl From<String> for Command {
33 fn from(str: String) -> Self {
34 let paras: Vec<String> = str
35 .split_whitespace()
36 .collect::<Vec<&str>>()
37 .iter()
38 .map(|s| s.to_string())
39 .collect();
40 Command(paras)
41 }
42}
43
44impl From<Vec<String>> for Command {
45 fn from(strs: Vec<String>) -> Self {
46 Command::from(&strs[..])
47 }
48}
49
50impl From<&[String]> for Command {
51 fn from(strs: &[String]) -> Self {
52 Command(strs.to_vec())
53 }
54}
55
56impl From<&str> for Command {
57 fn from(str: &str) -> Self {
58 let paras: Vec<String> = str
59 .split_whitespace()
60 .collect::<Vec<&str>>()
61 .iter()
62 .map(|s| s.to_string())
63 .collect();
64 Command(paras)
65 }
66}
67
68impl FromStr for Command {
69 type Err = Error;
70
71 fn from_str(string: &str) -> Result<Self> {
73 let paras: Vec<String> = string
74 .split_whitespace()
75 .collect::<Vec<&str>>()
76 .iter()
77 .map(|s| s.to_string())
78 .collect();
79 Ok(Command(paras))
80 }
81}
82
83impl Serialize for Command {
84 fn serialize<S>(&self, ser: S) -> ::std::result::Result<S::Ok, S::Error>
85 where
86 S: Serializer,
87 {
88 self.0.serialize(ser)
89 }
90}
91
92#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
100pub struct Step {
101 #[serde(rename = "_type")]
102 pub typ: String,
103 pub threshold: u32,
104 pub name: String,
105 pub expected_materials: Vec<ArtifactRule>,
106 pub expected_products: Vec<ArtifactRule>,
107 #[serde(rename = "pubkeys")]
108 pub pub_keys: Vec<KeyId>,
109 pub expected_command: Command,
110}
111
112impl Step {
113 pub fn new(name: &str) -> Self {
114 Step {
115 pub_keys: Vec::new(),
116 expected_command: Command::default(),
117 threshold: 0,
118 name: name.into(),
119 expected_materials: Vec::new(),
120 expected_products: Vec::new(),
121 typ: "step".into(),
122 }
123 }
124
125 pub fn add_key(mut self, key: KeyId) -> Self {
127 self.pub_keys.push(key);
128 self
129 }
130
131 pub fn expected_command(mut self, command: Command) -> Self {
133 self.expected_command = command;
134 self
135 }
136
137 pub fn threshold(mut self, threshold: u32) -> Self {
139 self.threshold = threshold;
140 self
141 }
142
143 supply_chain_item_derive!();
145}
146
147impl SupplyChainItem for Step {
148 fn name(&self) -> &str {
149 &self.name
150 }
151
152 fn expected_materials(&self) -> &Vec<ArtifactRule> {
153 &self.expected_materials
154 }
155
156 fn expected_products(&self) -> &Vec<ArtifactRule> {
157 &self.expected_products
158 }
159}
160
161#[cfg(test)]
162mod test {
163 use std::str::FromStr;
164
165 use serde_json::json;
166
167 use crate::{
168 crypto::KeyId,
169 models::rule::{Artifact, ArtifactRule},
170 Result,
171 };
172
173 use super::Step;
174
175 #[test]
176 fn serialize_step() -> Result<()> {
177 let step = Step::new("package")
178 .add_expected_material(ArtifactRule::Match {
179 pattern: "foo.py".into(),
180 in_src: None,
181 with: Artifact::Products,
182 in_dst: None,
183 from: "write-code".into(),
184 })
185 .add_expected_product(ArtifactRule::Create("foo.tar.gz".into()))
186 .expected_command("tar zcvf foo.tar.gz foo.py".into())
187 .add_key(KeyId::from_str(
188 "70ca5750c2eda80b18f41f4ec5f92146789b5d68dd09577be422a0159bd13680",
189 )?)
190 .threshold(1);
191
192 let json_serialize = serde_json::to_value(&step)?;
193 let json = json!(
194 {
195 "_type": "step",
196 "name": "package",
197 "expected_materials": [
198 ["MATCH", "foo.py", "WITH", "PRODUCTS", "FROM", "write-code"]
199 ],
200 "expected_products": [
201 ["CREATE", "foo.tar.gz"]
202 ],
203 "expected_command": ["tar", "zcvf", "foo.tar.gz", "foo.py"],
204 "pubkeys": [
205 "70ca5750c2eda80b18f41f4ec5f92146789b5d68dd09577be422a0159bd13680"
206 ],
207 "threshold": 1
208 }
209 );
210 assert_eq!(
211 json, json_serialize,
212 "{:#?} != {:#?}",
213 json, json_serialize
214 );
215 Ok(())
216 }
217
218 #[test]
219 fn deserialize_step() -> Result<()> {
220 let json = r#"
221 {
222 "_type": "step",
223 "name": "package",
224 "expected_materials": [
225 ["MATCH", "foo.py", "WITH", "PRODUCTS", "FROM", "write-code"]
226 ],
227 "expected_products": [
228 ["CREATE", "foo.tar.gz"]
229 ],
230 "expected_command": ["tar", "zcvf", "foo.tar.gz", "foo.py"],
231 "pubkeys": [
232 "70ca5750c2eda80b18f41f4ec5f92146789b5d68dd09577be422a0159bd13680"
233 ],
234 "threshold": 1
235 }"#;
236 let step_parsed: Step = serde_json::from_str(json)?;
237 let step = Step::new("package")
238 .add_expected_material(ArtifactRule::Match {
239 pattern: "foo.py".into(),
240 in_src: None,
241 with: Artifact::Products,
242 in_dst: None,
243 from: "write-code".into(),
244 })
245 .add_expected_product(ArtifactRule::Create("foo.tar.gz".into()))
246 .expected_command("tar zcvf foo.tar.gz foo.py".into())
247 .add_key(KeyId::from_str(
248 "70ca5750c2eda80b18f41f4ec5f92146789b5d68dd09577be422a0159bd13680",
249 )?)
250 .threshold(1);
251 assert_eq!(step_parsed, step);
252 Ok(())
253 }
254}