Skip to main content

stix_rs/sdos/
location.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3
4use crate::common::{CommonProperties, StixObject};
5
6/// Location SDO
7#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
8#[serde(rename_all = "snake_case")]
9pub struct Location {
10    #[serde(flatten)]
11    pub common: CommonProperties,
12
13    pub name: Option<String>,
14    pub description: Option<String>,
15
16    pub latitude: Option<f64>,
17    pub longitude: Option<f64>,
18    pub precision: Option<f64>,
19
20    pub region: Option<String>,
21    pub country: Option<String>,
22    pub administrative_area: Option<String>,
23    pub city: Option<String>,
24    pub street_address: Option<String>,
25    pub postal_code: Option<String>,
26}
27
28impl Location {
29    pub fn builder() -> LocationBuilder {
30        LocationBuilder::default()
31    }
32}
33
34#[derive(Debug, Default)]
35pub struct LocationBuilder {
36    name: Option<String>,
37    description: Option<String>,
38    latitude: Option<f64>,
39    longitude: Option<f64>,
40    precision: Option<f64>,
41    region: Option<String>,
42    country: Option<String>,
43    administrative_area: Option<String>,
44    city: Option<String>,
45    street_address: Option<String>,
46    postal_code: Option<String>,
47    created_by_ref: Option<String>,
48}
49
50impl LocationBuilder {
51    pub fn name(mut self, n: impl Into<String>) -> Self {
52        self.name = Some(n.into());
53        self
54    }
55    pub fn description(mut self, d: impl Into<String>) -> Self {
56        self.description = Some(d.into());
57        self
58    }
59    pub fn latitude(mut self, lat: f64) -> Self {
60        self.latitude = Some(lat);
61        self
62    }
63    pub fn longitude(mut self, lon: f64) -> Self {
64        self.longitude = Some(lon);
65        self
66    }
67    pub fn precision(mut self, p: f64) -> Self {
68        self.precision = Some(p);
69        self
70    }
71    pub fn region(mut self, r: impl Into<String>) -> Self {
72        self.region = Some(r.into());
73        self
74    }
75    pub fn country(mut self, c: impl Into<String>) -> Self {
76        self.country = Some(c.into());
77        self
78    }
79    pub fn administrative_area(mut self, a: impl Into<String>) -> Self {
80        self.administrative_area = Some(a.into());
81        self
82    }
83    pub fn city(mut self, c: impl Into<String>) -> Self {
84        self.city = Some(c.into());
85        self
86    }
87    pub fn street_address(mut self, s: impl Into<String>) -> Self {
88        self.street_address = Some(s.into());
89        self
90    }
91    pub fn postal_code(mut self, p: impl Into<String>) -> Self {
92        self.postal_code = Some(p.into());
93        self
94    }
95    pub fn created_by_ref(mut self, r: impl Into<String>) -> Self {
96        self.created_by_ref = Some(r.into());
97        self
98    }
99
100    pub fn build(self) -> Result<Location, super::BuilderError> {
101        // STIX requires at least one of region, country, or (latitude and longitude).
102        if self.region.is_none()
103            && self.country.is_none()
104            && !(self.latitude.is_some() && self.longitude.is_some())
105        {
106            return Err(super::BuilderError::MissingField(
107                "one of region|country|(latitude+longitude)",
108            ));
109        }
110
111        let common = CommonProperties::new("location", self.created_by_ref);
112        Ok(Location {
113            common,
114            name: self.name,
115            description: self.description,
116            latitude: self.latitude,
117            longitude: self.longitude,
118            precision: self.precision,
119            region: self.region,
120            country: self.country,
121            administrative_area: self.administrative_area,
122            city: self.city,
123            street_address: self.street_address,
124            postal_code: self.postal_code,
125        })
126    }
127}
128
129impl StixObject for Location {
130    fn id(&self) -> &str {
131        &self.common.id
132    }
133    fn type_(&self) -> &str {
134        &self.common.r#type
135    }
136    fn created(&self) -> DateTime<Utc> {
137        self.common.created
138    }
139}
140
141impl From<Location> for crate::StixObjectEnum {
142    fn from(l: Location) -> Self {
143        crate::StixObjectEnum::Location(l)
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use super::*;
150    use serde_json::Value;
151
152    #[test]
153    fn location_builder_and_serialize() {
154        let loc = Location::builder().region("europe").build().unwrap();
155        let s = serde_json::to_string(&loc).unwrap();
156        let v: Value = serde_json::from_str(&s).unwrap();
157        assert_eq!(v.get("type").and_then(Value::as_str).unwrap(), "location");
158        assert!(v.get("region").is_some());
159    }
160}