1use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8
9use crate::common::ExternalReference;
10
11#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
15#[serde(rename_all = "snake_case")]
16pub struct Name {
17 pub value: String,
19
20 #[serde(skip_serializing_if = "Option::is_none")]
22 pub source: Option<ExternalReference>,
23
24 #[serde(skip_serializing_if = "Option::is_none")]
26 pub confidence: Option<String>,
27}
28
29impl Name {
30 pub fn new(value: impl Into<String>) -> Self {
32 Self {
33 value: value.into(),
34 source: None,
35 confidence: None,
36 }
37 }
38
39 pub fn with_source(value: impl Into<String>, source: ExternalReference) -> Self {
41 Self {
42 value: value.into(),
43 source: Some(source),
44 confidence: None,
45 }
46 }
47
48 pub fn with_confidence(
50 value: impl Into<String>,
51 source: ExternalReference,
52 confidence: impl Into<String>,
53 ) -> Self {
54 Self {
55 value: value.into(),
56 source: Some(source),
57 confidence: Some(confidence.into()),
58 }
59 }
60}
61
62impl From<String> for Name {
63 fn from(value: String) -> Self {
64 Name::new(value)
65 }
66}
67
68impl From<&str> for Name {
69 fn from(value: &str) -> Self {
70 Name::new(value)
71 }
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
79#[serde(rename_all = "snake_case")]
80pub struct FieldData {
81 #[serde(skip_serializing_if = "Option::is_none")]
83 pub delivery_vectors: Option<Vec<String>>,
84
85 #[serde(skip_serializing_if = "Option::is_none")]
87 pub first_seen: Option<DateTime<Utc>>,
88
89 #[serde(skip_serializing_if = "Option::is_none")]
91 pub last_seen: Option<DateTime<Utc>>,
92}
93
94impl FieldData {
95 pub fn builder() -> FieldDataBuilder {
97 FieldDataBuilder::default()
98 }
99
100 pub fn with_delivery_vectors(vectors: Vec<String>) -> Self {
102 Self {
103 delivery_vectors: Some(vectors),
104 first_seen: None,
105 last_seen: None,
106 }
107 }
108
109 pub fn with_timestamps(first_seen: DateTime<Utc>, last_seen: Option<DateTime<Utc>>) -> Self {
111 Self {
112 delivery_vectors: None,
113 first_seen: Some(first_seen),
114 last_seen,
115 }
116 }
117}
118
119#[derive(Debug, Default)]
121pub struct FieldDataBuilder {
122 delivery_vectors: Option<Vec<String>>,
123 first_seen: Option<DateTime<Utc>>,
124 last_seen: Option<DateTime<Utc>>,
125}
126
127impl FieldDataBuilder {
128 pub fn delivery_vectors(mut self, vectors: Vec<String>) -> Self {
129 self.delivery_vectors = Some(vectors);
130 self
131 }
132
133 pub fn add_delivery_vector(mut self, vector: impl Into<String>) -> Self {
134 self.delivery_vectors
135 .get_or_insert_with(Vec::new)
136 .push(vector.into());
137 self
138 }
139
140 pub fn first_seen(mut self, timestamp: DateTime<Utc>) -> Self {
141 self.first_seen = Some(timestamp);
142 self
143 }
144
145 pub fn last_seen(mut self, timestamp: DateTime<Utc>) -> Self {
146 self.last_seen = Some(timestamp);
147 self
148 }
149
150 pub fn build(self) -> crate::error::Result<FieldData> {
151 if self.delivery_vectors.is_none() && self.first_seen.is_none() && self.last_seen.is_none()
153 {
154 return Err(crate::error::MaecError::ValidationError(
155 "FieldData must have at least one of: delivery_vectors, first_seen, or last_seen"
156 .to_string(),
157 ));
158 }
159
160 Ok(FieldData {
161 delivery_vectors: self.delivery_vectors,
162 first_seen: self.first_seen,
163 last_seen: self.last_seen,
164 })
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171
172 #[test]
173 fn test_name_new() {
174 let name = Name::new("WannaCry");
175 assert_eq!(name.value, "WannaCry");
176 assert!(name.source.is_none());
177 assert!(name.confidence.is_none());
178 }
179
180 #[test]
181 fn test_name_from_string() {
182 let name: Name = "Emotet".into();
183 assert_eq!(name.value, "Emotet");
184 }
185
186 #[test]
187 fn test_field_data_builder() {
188 let field_data = FieldData::builder()
189 .add_delivery_vector("email")
190 .first_seen(Utc::now())
191 .build()
192 .unwrap();
193
194 assert!(field_data.delivery_vectors.is_some());
195 assert!(field_data.first_seen.is_some());
196 }
197
198 #[test]
199 fn test_field_data_validation() {
200 let result = FieldData::builder().build();
201 assert!(result.is_err());
202
203 let valid = FieldData::builder().add_delivery_vector("email").build();
204 assert!(valid.is_ok());
205 }
206}