use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use uuid::Uuid;
use crate::core::{Location, SourceRef, Timestamp};
use crate::error::{Error, Result};
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct EventId(pub Uuid);
impl EventId {
pub fn new() -> Self {
Self(Uuid::new_v4())
}
pub fn from_uuid(uuid: Uuid) -> Self {
Self(uuid)
}
pub fn parse(s: &str) -> Result<Self> {
Uuid::parse_str(s)
.map(Self)
.map_err(|_| Error::ParseError(format!("invalid event ID: {}", s)))
}
pub fn as_uuid(&self) -> &Uuid {
&self.0
}
}
impl Default for EventId {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Display for EventId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<Uuid> for EventId {
fn from(uuid: Uuid) -> Self {
Self(uuid)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Event {
pub id: EventId,
pub location: Location,
pub timestamp: Timestamp,
pub text: String,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub metadata: HashMap<String, String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub sources: Vec<SourceRef>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tags: Vec<String>,
}
impl Event {
pub fn new(location: Location, timestamp: Timestamp, text: impl Into<String>) -> Self {
Self {
id: EventId::new(),
location,
timestamp,
text: text.into(),
metadata: HashMap::new(),
sources: Vec::new(),
tags: Vec::new(),
}
}
pub fn builder() -> EventBuilder {
EventBuilder::new()
}
pub fn has_tag(&self, tag: &str) -> bool {
self.tags.iter().any(|t| t == tag)
}
pub fn add_tag(&mut self, tag: impl Into<String>) {
let tag = tag.into();
if !self.has_tag(&tag) {
self.tags.push(tag);
}
}
pub fn remove_tag(&mut self, tag: &str) {
self.tags.retain(|t| t != tag);
}
pub fn get_metadata(&self, key: &str) -> Option<&str> {
self.metadata.get(key).map(|s| s.as_str())
}
pub fn set_metadata(&mut self, key: impl Into<String>, value: impl Into<String>) {
self.metadata.insert(key.into(), value.into());
}
pub fn add_source(&mut self, source: SourceRef) {
self.sources.push(source);
}
pub fn to_geo_point(&self) -> geo_types::Point<f64> {
self.location.to_geo_point()
}
}
#[derive(Debug, Default)]
pub struct EventBuilder {
id: Option<EventId>,
location: Option<Location>,
timestamp: Option<Timestamp>,
text: Option<String>,
metadata: HashMap<String, String>,
sources: Vec<SourceRef>,
tags: Vec<String>,
}
impl EventBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn id(mut self, id: EventId) -> Self {
self.id = Some(id);
self
}
pub fn location(mut self, location: Location) -> Self {
self.location = Some(location);
self
}
pub fn coordinates(mut self, lat: f64, lon: f64) -> Self {
self.location = Some(Location::new(lat, lon));
self
}
pub fn timestamp(mut self, timestamp: Timestamp) -> Self {
self.timestamp = Some(timestamp);
self
}
pub fn timestamp_str(mut self, s: &str) -> Result<Self> {
self.timestamp = Some(Timestamp::parse(s)?);
Ok(self)
}
pub fn text(mut self, text: impl Into<String>) -> Self {
self.text = Some(text.into());
self
}
pub fn tag(mut self, tag: impl Into<String>) -> Self {
self.tags.push(tag.into());
self
}
pub fn tags(mut self, tags: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.tags.extend(tags.into_iter().map(Into::into));
self
}
pub fn source(mut self, source: SourceRef) -> Self {
self.sources.push(source);
self
}
pub fn sources(mut self, sources: impl IntoIterator<Item = SourceRef>) -> Self {
self.sources.extend(sources);
self
}
pub fn metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.metadata.insert(key.into(), value.into());
self
}
pub fn build(self) -> Event {
Event {
id: self.id.unwrap_or_default(),
location: self.location.unwrap_or_default(),
timestamp: self.timestamp.unwrap_or_else(Timestamp::now),
text: self.text.unwrap_or_default(),
metadata: self.metadata,
sources: self.sources,
tags: self.tags,
}
}
pub fn try_build(self) -> Result<Event> {
let location = self.location.ok_or(Error::MissingField("location"))?;
let timestamp = self.timestamp.ok_or(Error::MissingField("timestamp"))?;
let text = self.text.ok_or(Error::MissingField("text"))?;
Ok(Event {
id: self.id.unwrap_or_default(),
location,
timestamp,
text,
metadata: self.metadata,
sources: self.sources,
tags: self.tags,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_event_id_new() {
let id1 = EventId::new();
let id2 = EventId::new();
assert_ne!(id1, id2);
}
#[test]
fn test_event_id_parse() {
let id = EventId::new();
let parsed = EventId::parse(&id.to_string()).unwrap();
assert_eq!(id, parsed);
}
#[test]
fn test_event_new() {
let event = Event::new(
Location::new(40.7128, -74.0060),
Timestamp::now(),
"Test event",
);
assert_eq!(event.text, "Test event");
assert!(event.tags.is_empty());
}
#[test]
fn test_event_builder() {
let event = Event::builder()
.location(Location::new(40.7128, -74.0060))
.timestamp(Timestamp::now())
.text("Protest at City Hall")
.tag("protest")
.tag("politics")
.metadata("participants", "1000")
.source(SourceRef::article("https://example.com"))
.build();
assert_eq!(event.text, "Protest at City Hall");
assert!(event.has_tag("protest"));
assert!(event.has_tag("politics"));
assert_eq!(event.get_metadata("participants"), Some("1000"));
assert_eq!(event.sources.len(), 1);
}
#[test]
fn test_event_try_build_missing_fields() {
let result = Event::builder().try_build();
assert!(result.is_err());
}
#[test]
fn test_event_tags() {
let mut event = Event::new(Location::new(0.0, 0.0), Timestamp::now(), "Test");
event.add_tag("tag1");
event.add_tag("tag2");
event.add_tag("tag1");
assert_eq!(event.tags.len(), 2);
assert!(event.has_tag("tag1"));
assert!(event.has_tag("tag2"));
event.remove_tag("tag1");
assert!(!event.has_tag("tag1"));
}
#[test]
fn test_event_metadata() {
let mut event = Event::builder()
.location(Location::new(0.0, 0.0))
.timestamp(Timestamp::now())
.text("Test")
.metadata("key1", "value1")
.build();
assert_eq!(event.get_metadata("key1"), Some("value1"));
assert_eq!(event.get_metadata("key2"), None);
event.set_metadata("key2", "value2");
assert_eq!(event.get_metadata("key2"), Some("value2"));
}
#[test]
fn test_event_serialization() {
let event = Event::builder()
.location(Location::new(40.7128, -74.0060))
.timestamp(Timestamp::parse("2024-03-15T14:30:00Z").unwrap())
.text("Test event")
.tag("test")
.build();
let json = serde_json::to_string(&event).unwrap();
let parsed: Event = serde_json::from_str(&json).unwrap();
assert_eq!(event.text, parsed.text);
assert_eq!(event.location.lat, parsed.location.lat);
}
}