Skip to main content

stix_rs/sdos/
report.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3
4use crate::common::{CommonProperties, StixObject};
5use crate::sdos::BuilderError;
6
7/// Report SDO
8#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
9#[serde(rename_all = "snake_case")]
10pub struct Report {
11    #[serde(flatten)]
12    pub common: CommonProperties,
13    pub name: String,
14    pub published: Option<DateTime<Utc>>,
15    pub report_types: Option<Vec<String>>,
16    pub object_refs: Option<Vec<String>>,
17}
18
19impl Report {
20    pub fn builder() -> ReportBuilder {
21        ReportBuilder::default()
22    }
23}
24
25#[derive(Debug, Default)]
26pub struct ReportBuilder {
27    name: Option<String>,
28    published: Option<DateTime<Utc>>,
29    report_types: Option<Vec<String>>,
30    object_refs: Option<Vec<String>>,
31    created_by_ref: Option<String>,
32}
33
34impl ReportBuilder {
35    pub fn name(mut self, name: impl Into<String>) -> Self {
36        self.name = Some(name.into());
37        self
38    }
39
40    pub fn published(mut self, t: DateTime<Utc>) -> Self {
41        self.published = Some(t);
42        self
43    }
44
45    pub fn report_types(mut self, r: Vec<String>) -> Self {
46        self.report_types = Some(r);
47        self
48    }
49
50    pub fn object_refs(mut self, o: Vec<String>) -> Self {
51        self.object_refs = Some(o);
52        self
53    }
54
55    pub fn created_by_ref(mut self, r: impl Into<String>) -> Self {
56        self.created_by_ref = Some(r.into());
57        self
58    }
59
60    pub fn build(self) -> Result<Report, BuilderError> {
61        let name = self.name.ok_or(BuilderError::MissingField("name"))?;
62        let common = CommonProperties::new("report", self.created_by_ref);
63        Ok(Report {
64            common,
65            name,
66            published: self.published,
67            report_types: self.report_types,
68            object_refs: self.object_refs,
69        })
70    }
71}
72
73impl StixObject for Report {
74    fn id(&self) -> &str {
75        &self.common.id
76    }
77
78    fn type_(&self) -> &str {
79        &self.common.r#type
80    }
81
82    fn created(&self) -> DateTime<Utc> {
83        self.common.created
84    }
85}
86
87impl From<Report> for crate::StixObjectEnum {
88    fn from(r: Report) -> Self {
89        crate::StixObjectEnum::Report(r)
90    }
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96    use chrono::Utc;
97
98    #[test]
99    fn report_builder() {
100        let report = Report::builder()
101            .name("Threat Intelligence Report Q1 2024")
102            .published(Utc::now())
103            .report_types(vec!["threat-actor".into()])
104            .object_refs(vec!["malware--1234".into()])
105            .build()
106            .unwrap();
107
108        assert_eq!(report.name, "Threat Intelligence Report Q1 2024");
109        assert_eq!(report.common.r#type, "report");
110    }
111
112    #[test]
113    fn report_serialize() {
114        let report = Report::builder().name("APT Analysis").build().unwrap();
115
116        let json = serde_json::to_string(&report).unwrap();
117        assert!(json.contains("\"type\":\"report\""));
118        assert!(json.contains("\"name\":\"APT Analysis\""));
119    }
120}