1use crate::core::Timestamp;
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
8#[serde(rename_all = "lowercase")]
9pub enum SourceType {
10 Article,
12 Report,
14 Witness,
16 Sensor,
18 Social,
20 Academic,
22 Government,
24 Archive,
26 #[default]
28 Other,
29}
30
31impl std::fmt::Display for SourceType {
32 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33 match self {
34 SourceType::Article => write!(f, "article"),
35 SourceType::Report => write!(f, "report"),
36 SourceType::Witness => write!(f, "witness"),
37 SourceType::Sensor => write!(f, "sensor"),
38 SourceType::Social => write!(f, "social"),
39 SourceType::Academic => write!(f, "academic"),
40 SourceType::Government => write!(f, "government"),
41 SourceType::Archive => write!(f, "archive"),
42 SourceType::Other => write!(f, "other"),
43 }
44 }
45}
46
47#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
67pub struct SourceRef {
68 pub source_type: SourceType,
70 #[serde(skip_serializing_if = "Option::is_none")]
72 pub url: Option<String>,
73 #[serde(skip_serializing_if = "Option::is_none")]
75 pub title: Option<String>,
76 #[serde(skip_serializing_if = "Option::is_none")]
78 pub author: Option<String>,
79 #[serde(skip_serializing_if = "Option::is_none")]
81 pub date: Option<Timestamp>,
82 #[serde(skip_serializing_if = "Option::is_none")]
84 pub notes: Option<String>,
85}
86
87impl SourceRef {
88 pub fn new(source_type: SourceType) -> Self {
90 Self {
91 source_type,
92 url: None,
93 title: None,
94 author: None,
95 date: None,
96 notes: None,
97 }
98 }
99
100 pub fn article(url: impl Into<String>) -> Self {
102 Self {
103 source_type: SourceType::Article,
104 url: Some(url.into()),
105 title: None,
106 author: None,
107 date: None,
108 notes: None,
109 }
110 }
111
112 pub fn report(url: impl Into<String>) -> Self {
114 Self {
115 source_type: SourceType::Report,
116 url: Some(url.into()),
117 title: None,
118 author: None,
119 date: None,
120 notes: None,
121 }
122 }
123
124 pub fn witness(notes: Option<String>) -> Self {
126 Self {
127 source_type: SourceType::Witness,
128 url: None,
129 title: None,
130 author: None,
131 date: None,
132 notes,
133 }
134 }
135
136 pub fn sensor(url: impl Into<String>) -> Self {
138 Self {
139 source_type: SourceType::Sensor,
140 url: Some(url.into()),
141 title: None,
142 author: None,
143 date: None,
144 notes: None,
145 }
146 }
147
148 pub fn builder() -> SourceRefBuilder {
150 SourceRefBuilder::new()
151 }
152
153 pub fn with_url(mut self, url: impl Into<String>) -> Self {
155 self.url = Some(url.into());
156 self
157 }
158
159 pub fn with_title(mut self, title: impl Into<String>) -> Self {
161 self.title = Some(title.into());
162 self
163 }
164
165 pub fn with_author(mut self, author: impl Into<String>) -> Self {
167 self.author = Some(author.into());
168 self
169 }
170
171 pub fn with_date(mut self, date: Timestamp) -> Self {
173 self.date = Some(date);
174 self
175 }
176}
177
178impl Default for SourceRef {
179 fn default() -> Self {
180 Self::new(SourceType::Other)
181 }
182}
183
184#[derive(Debug, Default)]
186pub struct SourceRefBuilder {
187 source_type: SourceType,
188 url: Option<String>,
189 title: Option<String>,
190 author: Option<String>,
191 date: Option<Timestamp>,
192 notes: Option<String>,
193}
194
195impl SourceRefBuilder {
196 pub fn new() -> Self {
198 Self::default()
199 }
200
201 pub fn source_type(mut self, source_type: SourceType) -> Self {
203 self.source_type = source_type;
204 self
205 }
206
207 pub fn url(mut self, url: impl Into<String>) -> Self {
209 self.url = Some(url.into());
210 self
211 }
212
213 pub fn title(mut self, title: impl Into<String>) -> Self {
215 self.title = Some(title.into());
216 self
217 }
218
219 pub fn author(mut self, author: impl Into<String>) -> Self {
221 self.author = Some(author.into());
222 self
223 }
224
225 pub fn date(mut self, date: Timestamp) -> Self {
227 self.date = Some(date);
228 self
229 }
230
231 pub fn notes(mut self, notes: impl Into<String>) -> Self {
233 self.notes = Some(notes.into());
234 self
235 }
236
237 pub fn build(self) -> SourceRef {
239 SourceRef {
240 source_type: self.source_type,
241 url: self.url,
242 title: self.title,
243 author: self.author,
244 date: self.date,
245 notes: self.notes,
246 }
247 }
248}
249
250#[cfg(test)]
251mod tests {
252 use super::*;
253
254 #[test]
255 fn test_source_article() {
256 let source = SourceRef::article("https://example.com/news");
257 assert_eq!(source.source_type, SourceType::Article);
258 assert_eq!(source.url, Some("https://example.com/news".to_string()));
259 }
260
261 #[test]
262 fn test_source_builder() {
263 let source = SourceRef::builder()
264 .source_type(SourceType::Report)
265 .url("https://example.com/report.pdf")
266 .title("Annual Report")
267 .author("Research Team")
268 .build();
269
270 assert_eq!(source.source_type, SourceType::Report);
271 assert_eq!(source.title, Some("Annual Report".to_string()));
272 assert_eq!(source.author, Some("Research Team".to_string()));
273 }
274
275 #[test]
276 fn test_source_with_methods() {
277 let source = SourceRef::new(SourceType::Academic)
278 .with_url("https://journal.example.com/paper")
279 .with_title("Research Paper")
280 .with_author("Dr. Smith");
281
282 assert_eq!(source.source_type, SourceType::Academic);
283 assert!(source.url.is_some());
284 assert!(source.title.is_some());
285 }
286
287 #[test]
288 fn test_source_serialization() {
289 let source = SourceRef::article("https://example.com");
290 let json = serde_json::to_string(&source).unwrap();
291 let parsed: SourceRef = serde_json::from_str(&json).unwrap();
292 assert_eq!(source, parsed);
293 }
294}