1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3
4use crate::common::{CommonProperties, StixObject};
5use crate::sdos::BuilderError;
6
7#[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}