Skip to main content

spatial_narrative/core/
source.rs

1//! Source reference types for tracking data provenance.
2
3use crate::core::Timestamp;
4use serde::{Deserialize, Serialize};
5
6/// Type of source material.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
8#[serde(rename_all = "lowercase")]
9pub enum SourceType {
10    /// News article or blog post.
11    Article,
12    /// Official report or document.
13    Report,
14    /// Witness account or testimony.
15    Witness,
16    /// Sensor or automated data.
17    Sensor,
18    /// Social media post.
19    Social,
20    /// Academic paper or research.
21    Academic,
22    /// Government or official record.
23    Government,
24    /// Archive or historical document.
25    Archive,
26    /// Other/unknown source type.
27    #[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/// Reference to source material.
48///
49/// Sources track the provenance of event data, enabling
50/// verification and attribution.
51///
52/// # Examples
53///
54/// ```
55/// use spatial_narrative::core::{SourceRef, SourceType};
56///
57/// let source = SourceRef::article("https://news.example.com/story");
58///
59/// let detailed = SourceRef::builder()
60///     .source_type(SourceType::Report)
61///     .url("https://gov.example.com/report.pdf")
62///     .title("Official Incident Report")
63///     .author("Department of Safety")
64///     .build();
65/// ```
66#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
67pub struct SourceRef {
68    /// Type of source.
69    pub source_type: SourceType,
70    /// URL to the source (if available).
71    #[serde(skip_serializing_if = "Option::is_none")]
72    pub url: Option<String>,
73    /// Title of the source.
74    #[serde(skip_serializing_if = "Option::is_none")]
75    pub title: Option<String>,
76    /// Author or creator.
77    #[serde(skip_serializing_if = "Option::is_none")]
78    pub author: Option<String>,
79    /// Publication or access date.
80    #[serde(skip_serializing_if = "Option::is_none")]
81    pub date: Option<Timestamp>,
82    /// Additional notes about the source.
83    #[serde(skip_serializing_if = "Option::is_none")]
84    pub notes: Option<String>,
85}
86
87impl SourceRef {
88    /// Creates a new source reference with the given type.
89    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    /// Creates an article source with a URL.
101    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    /// Creates a report source with a URL.
113    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    /// Creates a witness source with optional notes.
125    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    /// Creates a sensor source with a URL.
137    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    /// Creates a builder for constructing a SourceRef.
149    pub fn builder() -> SourceRefBuilder {
150        SourceRefBuilder::new()
151    }
152
153    /// Sets the URL.
154    pub fn with_url(mut self, url: impl Into<String>) -> Self {
155        self.url = Some(url.into());
156        self
157    }
158
159    /// Sets the title.
160    pub fn with_title(mut self, title: impl Into<String>) -> Self {
161        self.title = Some(title.into());
162        self
163    }
164
165    /// Sets the author.
166    pub fn with_author(mut self, author: impl Into<String>) -> Self {
167        self.author = Some(author.into());
168        self
169    }
170
171    /// Sets the date.
172    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/// Builder for constructing [`SourceRef`] instances.
185#[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    /// Creates a new SourceRefBuilder.
197    pub fn new() -> Self {
198        Self::default()
199    }
200
201    /// Sets the source type.
202    pub fn source_type(mut self, source_type: SourceType) -> Self {
203        self.source_type = source_type;
204        self
205    }
206
207    /// Sets the URL.
208    pub fn url(mut self, url: impl Into<String>) -> Self {
209        self.url = Some(url.into());
210        self
211    }
212
213    /// Sets the title.
214    pub fn title(mut self, title: impl Into<String>) -> Self {
215        self.title = Some(title.into());
216        self
217    }
218
219    /// Sets the author.
220    pub fn author(mut self, author: impl Into<String>) -> Self {
221        self.author = Some(author.into());
222        self
223    }
224
225    /// Sets the date.
226    pub fn date(mut self, date: Timestamp) -> Self {
227        self.date = Some(date);
228        self
229    }
230
231    /// Sets the notes.
232    pub fn notes(mut self, notes: impl Into<String>) -> Self {
233        self.notes = Some(notes.into());
234        self
235    }
236
237    /// Builds the SourceRef.
238    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}