1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3
4use crate::common::{CommonProperties, StixObject};
5
6#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
8#[serde(rename_all = "snake_case")]
9pub struct Grouping {
10 #[serde(flatten)]
11 pub common: CommonProperties,
12
13 pub name: Option<String>,
14 pub description: Option<String>,
15 pub context: String,
16 pub object_refs: Vec<String>,
17}
18
19impl Grouping {
20 pub fn builder() -> GroupingBuilder {
21 GroupingBuilder::default()
22 }
23}
24
25#[derive(Debug, Default)]
26pub struct GroupingBuilder {
27 name: Option<String>,
28 description: Option<String>,
29 context: Option<String>,
30 object_refs: Option<Vec<String>>,
31 created_by_ref: Option<String>,
32}
33
34impl GroupingBuilder {
35 pub fn name(mut self, name: impl Into<String>) -> Self {
36 self.name = Some(name.into());
37 self
38 }
39 pub fn description(mut self, d: impl Into<String>) -> Self {
40 self.description = Some(d.into());
41 self
42 }
43 pub fn context(mut self, c: impl Into<String>) -> Self {
44 self.context = Some(c.into());
45 self
46 }
47 pub fn object_refs(mut self, o: Vec<String>) -> Self {
48 self.object_refs = Some(o);
49 self
50 }
51 pub fn created_by_ref(mut self, r: impl Into<String>) -> Self {
52 self.created_by_ref = Some(r.into());
53 self
54 }
55
56 pub fn build(self) -> Result<Grouping, super::BuilderError> {
57 let context = self
58 .context
59 .ok_or(super::BuilderError::MissingField("context"))?;
60 let object_refs = self
61 .object_refs
62 .ok_or(super::BuilderError::MissingField("object_refs"))?;
63 let common = CommonProperties::new("grouping", self.created_by_ref);
64 Ok(Grouping {
65 common,
66 name: self.name,
67 description: self.description,
68 context,
69 object_refs,
70 })
71 }
72}
73
74impl StixObject for Grouping {
75 fn id(&self) -> &str {
76 &self.common.id
77 }
78 fn type_(&self) -> &str {
79 &self.common.r#type
80 }
81 fn created(&self) -> DateTime<Utc> {
82 self.common.created
83 }
84}
85
86impl From<Grouping> for crate::StixObjectEnum {
87 fn from(g: Grouping) -> Self {
88 crate::StixObjectEnum::Grouping(g)
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95 use serde_json::Value;
96
97 #[test]
98 fn grouping_builder_and_serialize() {
99 let g = Grouping::builder()
100 .context("suspicious-activity")
101 .object_refs(vec!["file--1234".into()])
102 .build()
103 .unwrap();
104
105 let s = serde_json::to_string(&g).unwrap();
106 let v: Value = serde_json::from_str(&s).unwrap();
107 assert_eq!(v.get("type").and_then(Value::as_str).unwrap(), "grouping");
108 assert!(v.get("object_refs").is_some());
109 }
110}