use crate::proto::{CotEvent, Detail, Contact, Track, Group, Status, Takv, PrecisionLocation};
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Debug, Clone)]
pub struct CotEventBuilder {
event: CotEvent,
detail: Detail,
stale_offset_ms: u64,
}
impl CotEventBuilder {
pub fn new() -> Self {
let now_ms = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis() as u64;
Self {
event: CotEvent {
r#type: String::new(),
access: String::new(),
qos: String::new(),
opex: String::new(),
uid: String::new(),
send_time: now_ms,
start_time: now_ms,
stale_time: now_ms + 60_000, how: String::new(),
lat: 0.0,
lon: 0.0,
hae: 0.0,
ce: 9999999.0,
le: 9999999.0,
detail: None,
},
detail: Detail {
xml_detail: String::new(),
contact: None,
group: None,
precision_location: None,
status: None,
takv: None,
track: None,
},
stale_offset_ms: 60_000,
}
}
pub fn cot_type(mut self, cot_type: &str) -> Self {
self.event.r#type = cot_type.to_string();
self
}
pub fn uid(mut self, uid: &str) -> Self {
self.event.uid = uid.to_string();
self
}
pub fn lat_lon(mut self, lat: f64, lon: f64) -> Self {
self.event.lat = lat;
self.event.lon = lon;
self
}
pub fn hae(mut self, hae: f64) -> Self {
self.event.hae = hae;
self
}
pub fn ce_le(mut self, ce: f64, le: f64) -> Self {
self.event.ce = ce;
self.event.le = le;
self
}
pub fn how(mut self, how: &str) -> Self {
self.event.how = how.to_string();
self
}
pub fn stale_minutes(mut self, minutes: u64) -> Self {
self.stale_offset_ms = minutes * 60_000;
self.event.stale_time = self.event.start_time + self.stale_offset_ms;
self
}
pub fn stale_seconds(mut self, seconds: u64) -> Self {
self.stale_offset_ms = seconds * 1000;
self.event.stale_time = self.event.start_time + self.stale_offset_ms;
self
}
pub fn timestamps(mut self, send_time: u64, start_time: u64, stale_time: u64) -> Self {
self.event.send_time = send_time;
self.event.start_time = start_time;
self.event.stale_time = stale_time;
self
}
pub fn with_contact(mut self, contact: Contact) -> Self {
self.detail.contact = Some(contact);
self
}
pub fn with_track(mut self, track: Track) -> Self {
self.detail.track = Some(track);
self
}
pub fn with_group(mut self, group: Group) -> Self {
self.detail.group = Some(group);
self
}
pub fn with_status(mut self, status: Status) -> Self {
self.detail.status = Some(status);
self
}
pub fn with_takv(mut self, takv: Takv) -> Self {
self.detail.takv = Some(takv);
self
}
pub fn with_precision_location(mut self, precision_location: PrecisionLocation) -> Self {
self.detail.precision_location = Some(precision_location);
self
}
pub fn with_xml_detail(mut self, xml_detail: &str) -> Self {
self.detail.xml_detail = xml_detail.to_string();
self
}
pub fn build(mut self) -> Result<CotEvent, String> {
if self.event.r#type.is_empty() {
return Err("CoT type is required".to_string());
}
if self.event.uid.is_empty() {
return Err("UID is required".to_string());
}
if self.detail.contact.is_some()
|| self.detail.group.is_some()
|| self.detail.precision_location.is_some()
|| self.detail.status.is_some()
|| self.detail.takv.is_some()
|| self.detail.track.is_some()
|| !self.detail.xml_detail.is_empty()
{
self.event.detail = Some(self.detail);
}
Ok(self.event)
}
}
impl Default for CotEventBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_builder_basic() {
let event = CotEventBuilder::new()
.uid("TEST-1")
.cot_type("a-f-G-U-C")
.lat_lon(37.7749, -122.4194)
.build()
.unwrap();
assert_eq!(event.uid, "TEST-1");
assert_eq!(event.r#type, "a-f-G-U-C");
assert_eq!(event.lat, 37.7749);
assert_eq!(event.lon, -122.4194);
}
#[test]
fn test_builder_with_details() {
use crate::helpers::{contact, track};
let event = CotEventBuilder::new()
.uid("TEST-2")
.cot_type("a-f-G-U-C")
.lat_lon(37.0, -122.0)
.with_contact(contact("ALPHA-1", None))
.with_track(track(15.0, 270.0))
.build()
.unwrap();
assert!(event.detail.is_some());
let detail = event.detail.unwrap();
assert!(detail.contact.is_some());
assert!(detail.track.is_some());
}
#[test]
fn test_builder_missing_required() {
let result = CotEventBuilder::new()
.uid("TEST-3")
.build();
assert!(result.is_err());
assert!(result.unwrap_err().contains("type"));
}
#[test]
fn test_stale_time() {
let event = CotEventBuilder::new()
.uid("TEST-4")
.cot_type("a-f-G-U-C")
.lat_lon(0.0, 0.0)
.stale_minutes(5)
.build()
.unwrap();
let diff = event.stale_time - event.start_time;
assert_eq!(diff, 5 * 60 * 1000);
}
}