cyclonedx_rust/
service.rs

1mod data_classification_type;
2mod data_flow_type;
3
4use crate::common::license::Licenses;
5use crate::common::organization::OrganizationalEntity;
6use crate::component::external_reference::ExternalReference;
7use crate::service::data_classification_type::DataClassificationType;
8use derive_builder::Builder;
9use serde::{Deserialize, Serialize};
10use yaserde_derive::{YaDeserialize, YaSerialize};
11
12#[derive(Clone, Builder, PartialEq, Debug, Serialize, Deserialize, YaSerialize, YaDeserialize)]
13pub struct Services {
14    pub service: Vec<Service>,
15}
16
17impl Services {
18    pub fn new(service: Vec<Service>) -> Services {
19        Services { service }
20    }
21}
22
23#[derive(Clone, Builder, PartialEq, Debug, Serialize, Deserialize, YaSerialize, YaDeserialize)]
24#[yaserde(
25    prefix = "ns",
26    default_namespace = "ns",
27    namespace = "ns: http://cyclonedx.org/schema/bom/1.2"
28)]
29pub struct Service {
30    #[serde(rename = "bom-ref")]
31    #[yaserde(rename = "bom-ref", attribute)]
32    pub bom_ref: Option<String>,
33
34    #[yaserde(prefix = "ns")]
35    pub provider: Option<OrganizationalEntity>,
36    #[yaserde(prefix = "ns")]
37    pub group: Option<String>,
38    #[yaserde(prefix = "ns")]
39    pub name: String,
40    #[yaserde(prefix = "ns")]
41    pub version: Option<String>,
42    #[yaserde(prefix = "ns")]
43    pub description: Option<String>,
44    #[yaserde(prefix = "ns")]
45    pub endpoints: Option<Endpoints>,
46    #[yaserde(prefix = "ns")]
47    pub authenticated: Option<bool>,
48    #[serde(rename = "x-trust-boundary")]
49    #[yaserde(rename = "x-trust-boundary", prefix = "ns")]
50    pub x_trust_boundary: Option<bool>,
51    pub data: Option<Classifications>,
52    pub licenses: Option<Licenses>,
53    #[serde(rename = "externalReferences")]
54    #[yaserde(rename = "externalReferences", prefix = "ns")]
55    pub external_references: Option<ExternalReferences>,
56    pub services: Vec<Service>,
57}
58
59#[derive(Clone, Default, PartialEq, Debug, Serialize, Deserialize, YaSerialize, YaDeserialize)]
60pub struct ExternalReferences {
61    pub reference: Vec<ExternalReference>,
62}
63
64impl ExternalReferences {
65    pub fn new(reference: Vec<ExternalReference>) -> ExternalReferences {
66        ExternalReferences { reference }
67    }
68}
69
70#[derive(Clone, Default, PartialEq, Debug, Serialize, Deserialize, YaSerialize, YaDeserialize)]
71pub struct Classifications {
72    pub classification: Vec<DataClassificationType>,
73}
74
75impl Classifications {
76    pub fn new(classification: Vec<DataClassificationType>) -> Classifications {
77        Classifications { classification }
78    }
79}
80
81#[derive(Clone, Default, PartialEq, Debug, Serialize, Deserialize, YaSerialize, YaDeserialize)]
82pub struct Endpoints {
83    pub endpoint: Vec<EndpointType>,
84}
85
86impl Endpoints {
87    pub fn new(endpoint: Vec<EndpointType>) -> Endpoints {
88        Endpoints { endpoint }
89    }
90}
91
92#[derive(Clone, PartialEq, Debug, Serialize, Deserialize, YaSerialize, YaDeserialize)]
93pub struct EndpointType {
94    #[yaserde(text)]
95    pub value: String,
96}
97
98impl EndpointType {
99    pub fn new(value: String) -> EndpointType {
100        EndpointType { value }
101    }
102}
103
104#[cfg(test)]
105pub mod tests {
106    use super::*;
107    use crate::common::license::*;
108    use crate::common::organization::*;
109    use crate::component::external_reference::*;
110    use crate::service::data_classification_type::*;
111    use crate::service::data_flow_type::DataFlowType;
112    use std::fs::File;
113    use std::io::BufReader;
114    use std::path::PathBuf;
115    use yaserde::ser::Config;
116
117    #[test]
118    pub fn print_xml() {
119        let expected = ServiceBuilder::default()
120            .bom_ref(Option::from(
121                "b2a46a4b-8367-4bae-9820-95557cfe03a8".to_string(),
122            ))
123            .provider(Option::from(
124                OrganizationalEntityBuilder::default()
125                    .name(Option::from("Partner Org".to_string()))
126                    .url(vec!["https://partner.org".to_string()])
127                    .contact(vec![OrganizationalContactBuilder::default()
128                        .name(Option::from("Support".to_string()))
129                        .email(vec!["support@partner".to_string()])
130                        .phone(vec!["800-555-1212".to_string()])
131                        .build()
132                        .unwrap()])
133                    .build()
134                    .unwrap(),
135            ))
136            .group(Option::from("org.partner".to_string()))
137            .name("Stock ticker service".to_string())
138            .version(Option::from("2020-Q2".to_string()))
139            .description(Option::from(
140                "Provides real-time stock information".to_string(),
141            ))
142            .endpoints(Option::from(Endpoints::new(vec![
143                EndpointType::new("https://partner.org/api/v1/lookup".to_string()),
144                EndpointType::new("https://partner.org/api/v1/stock".to_string()),
145            ])))
146            .authenticated(Option::from(true))
147            .x_trust_boundary(Option::from(true))
148            .data(Option::from(Classifications::new(vec![
149                DataClassificationTypeBuilder::default()
150                    .flow(DataFlowType::InBound)
151                    .value("PII".to_string())
152                    .build()
153                    .unwrap(),
154                DataClassificationTypeBuilder::default()
155                    .flow(DataFlowType::Outbound)
156                    .value("PIFI".to_string())
157                    .build()
158                    .unwrap(),
159                DataClassificationTypeBuilder::default()
160                    .flow(DataFlowType::BiDirectional)
161                    .value("public".to_string())
162                    .build()
163                    .unwrap(),
164            ])))
165            .licenses(Option::from(
166                LicensesBuilder::default()
167                    .license(vec![LicenseTypeBuilder::default()
168                        .id(None)
169                        .name(Option::from("Partner License".to_string()))
170                        .text(None)
171                        .url(None)
172                        .build()
173                        .unwrap()])
174                    .expression(None)
175                    .build()
176                    .unwrap(),
177            ))
178            .external_references(Option::from(ExternalReferences::new(vec![
179                ExternalReference::new(
180                    ExternalReferenceType::Website,
181                    "http://partner.org".to_string(),
182                    None,
183                ),
184                ExternalReference::new(
185                    ExternalReferenceType::Documentation,
186                    "http://api.partner.org/swagger".to_string(),
187                    None,
188                ),
189            ])))
190            .services(Vec::new())
191            .build()
192            .unwrap();
193
194        let parsed = yaserde::ser::to_string_with_config(
195            &expected,
196            &Config {
197                perform_indent: true,
198                write_document_declaration: false,
199                indent_string: None,
200            },
201        )
202        .unwrap();
203
204        let actual: Service = yaserde::de::from_str(parsed.as_str()).unwrap();
205
206        assert_eq!(expected, actual);
207    }
208
209    #[test]
210    pub fn can_decode() {
211        let reader = setup("service-1.2.xml");
212
213        let response: Service = yaserde::de::from_reader(reader).unwrap();
214
215        assert_eq!(response.name, "Stock ticker service");
216        assert_eq!(response.group.unwrap(), "org.partner");
217        assert_eq!(response.version.unwrap(), "2020-Q2");
218        assert_eq!(
219            response.description.unwrap(),
220            "Provides real-time stock information"
221        );
222        let endpoints = response.endpoints.unwrap();
223        assert_eq!(endpoints.endpoint.len(), 2);
224        assert_eq!(
225            endpoints.endpoint[0].value,
226            "https://partner.org/api/v1/lookup"
227        );
228        assert_eq!(
229            endpoints.endpoint[1].value,
230            "https://partner.org/api/v1/stock"
231        );
232        assert_eq!(response.authenticated.unwrap(), true);
233        assert_eq!(response.x_trust_boundary.unwrap(), true);
234
235        let classifications = response.data.unwrap().classification;
236        assert_eq!(classifications.len(), 3);
237        assert_eq!(classifications[0].flow, DataFlowType::InBound);
238        assert_eq!(classifications[0].value, "PII");
239        assert_eq!(classifications[1].flow, DataFlowType::Outbound);
240        assert_eq!(classifications[1].value, "PIFI");
241        assert_eq!(classifications[2].flow, DataFlowType::BiDirectional);
242        assert_eq!(classifications[2].value, "public");
243
244        let licenses = response.licenses.unwrap();
245        assert!(!licenses.expression.is_some());
246        assert_eq!(licenses.license.len(), 1);
247        let license_type = licenses.license[0].clone();
248        assert_eq!(license_type.name.unwrap(), "Partner license");
249
250        let references = response.external_references.unwrap().reference;
251        assert_eq!(references.len(), 2);
252        assert_eq!(references[0].ref_type, ExternalReferenceType::Website);
253        assert_eq!(references[0].url, "http://partner.org");
254        assert_eq!(references[1].ref_type, ExternalReferenceType::Documentation);
255        assert_eq!(references[1].url, "http://api.partner.org/swagger");
256    }
257
258    fn setup(file: &str) -> BufReader<File> {
259        let mut test_folder = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
260        test_folder.push("resources/test/".to_owned() + file);
261        let file = File::open(test_folder);
262        let reader = BufReader::new(file.unwrap());
263        reader
264    }
265}