1use serde::de::{Deserialize, Deserializer, Error as DeError};
4use serde::Serialize;
5
6use std::sync::Arc;
7
8use crate::error::{ModelError, Result};
9use crate::sdo::ObservedData;
10use crate::value::StixValue;
11use crate::view::{CustomObject, GenericObject, ObjectView};
12
13#[derive(Debug, Clone)]
16pub enum StixObject {
17 Typed(TypedObject),
19 Generic(GenericObject),
21 Custom(Arc<dyn CustomObject>),
23}
24
25#[derive(Debug, Clone, PartialEq)]
28pub enum TypedObject {
29 ObservedData(ObservedData),
31}
32
33impl StixObject {
34 pub fn from_json(value: serde_json::Value) -> Result<Self> {
36 let type_ = value
37 .get("type")
38 .and_then(serde_json::Value::as_str)
39 .ok_or_else(|| ModelError::InvalidObject("missing 'type' property".to_string()))?
40 .to_string();
41
42 match type_.as_str() {
43 "observed-data" => {
44 let od: ObservedData = serde_json::from_value(value)?;
45 Ok(StixObject::Typed(TypedObject::ObservedData(od)))
46 }
47 _ => Ok(StixObject::Generic(GenericObject::from_json(value)?)),
48 }
49 }
50
51 pub fn downcast_ref<T: 'static>(&self) -> Option<&T> {
53 match self {
54 StixObject::Custom(c) => c.as_any().downcast_ref::<T>(),
55 _ => None,
56 }
57 }
58}
59
60impl PartialEq for StixObject {
61 fn eq(&self, other: &Self) -> bool {
62 match (self, other) {
63 (StixObject::Typed(a), StixObject::Typed(b)) => a == b,
64 (StixObject::Generic(a), StixObject::Generic(b)) => a == b,
65 (StixObject::Custom(a), StixObject::Custom(b)) => a.as_json() == b.as_json(),
66 _ => false,
67 }
68 }
69}
70
71impl ObjectView for StixObject {
72 fn id(&self) -> Option<&str> {
73 match self {
74 StixObject::Typed(t) => t.id(),
75 StixObject::Generic(g) => g.id(),
76 StixObject::Custom(c) => c.id(),
77 }
78 }
79
80 fn type_(&self) -> Option<&str> {
81 match self {
82 StixObject::Typed(t) => t.type_(),
83 StixObject::Generic(g) => g.type_(),
84 StixObject::Custom(c) => c.type_(),
85 }
86 }
87
88 fn property(&self, name: &str) -> Option<StixValue> {
89 match self {
90 StixObject::Typed(t) => t.property(name),
91 StixObject::Generic(g) => g.property(name),
92 StixObject::Custom(c) => c.property(name),
93 }
94 }
95}
96
97impl ObjectView for TypedObject {
98 fn id(&self) -> Option<&str> {
99 match self {
100 TypedObject::ObservedData(od) => od.id(),
101 }
102 }
103
104 fn type_(&self) -> Option<&str> {
105 match self {
106 TypedObject::ObservedData(od) => od.type_(),
107 }
108 }
109
110 fn property(&self, name: &str) -> Option<StixValue> {
111 match self {
112 TypedObject::ObservedData(od) => od.property(name),
113 }
114 }
115}
116
117impl Serialize for StixObject {
118 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
119 where
120 S: serde::Serializer,
121 {
122 match self {
123 StixObject::Typed(TypedObject::ObservedData(od)) => od.serialize(serializer),
124 StixObject::Generic(g) => g.properties().serialize(serializer),
125 StixObject::Custom(c) => c.as_json().serialize(serializer),
126 }
127 }
128}
129
130impl<'de> Deserialize<'de> for StixObject {
131 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
132 where
133 D: Deserializer<'de>,
134 {
135 let value = serde_json::Value::deserialize(deserializer)?;
136 StixObject::from_json(value).map_err(DeError::custom)
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143 use crate::value::StixValue;
144 use crate::view::ObjectView;
145
146 #[test]
147 fn observed_data_becomes_typed() {
148 let v = serde_json::json!({
149 "type": "observed-data",
150 "id": "observed-data--1",
151 "first_observed": "2020-01-01T00:00:00Z",
152 "last_observed": "2020-01-01T00:05:00Z",
153 "number_observed": 1,
154 "object_refs": ["file--1"]
155 });
156 let obj = StixObject::from_json(v).unwrap();
157 assert!(matches!(
158 obj,
159 StixObject::Typed(TypedObject::ObservedData(_))
160 ));
161 assert_eq!(obj.type_(), Some("observed-data"));
162 }
163
164 #[test]
165 fn unknown_type_becomes_generic() {
166 let v = serde_json::json!({
167 "type": "ipv4-addr",
168 "id": "ipv4-addr--1",
169 "value": "198.51.100.1"
170 });
171 let obj = StixObject::from_json(v).unwrap();
172 assert!(matches!(obj, StixObject::Generic(_)));
173 assert_eq!(obj.type_(), Some("ipv4-addr"));
174 assert_eq!(
175 obj.property("value"),
176 Some(StixValue::String("198.51.100.1".into()))
177 );
178 }
179
180 #[test]
181 fn missing_type_is_an_error() {
182 let v = serde_json::json!({"id": "x--1"});
183 assert!(StixObject::from_json(v).is_err());
184 }
185
186 #[test]
187 fn deserializes_via_serde() {
188 let v = serde_json::json!({
190 "type": "ipv4-addr",
191 "id": "ipv4-addr--2",
192 "value": "203.0.113.5"
193 });
194 let obj: StixObject = serde_json::from_value(v).unwrap();
195 assert_eq!(obj.id(), Some("ipv4-addr--2"));
196 }
197
198 use std::sync::Arc;
199
200 #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
201 struct Widget {
202 #[serde(rename = "type")]
203 type_: String,
204 id: String,
205 risk: i64,
206 }
207
208 impl ObjectView for Widget {
209 fn id(&self) -> Option<&str> {
210 Some(&self.id)
211 }
212 fn type_(&self) -> Option<&str> {
213 Some(&self.type_)
214 }
215 fn property(&self, name: &str) -> Option<StixValue> {
216 match name {
217 "type" => Some(StixValue::String(self.type_.clone())),
218 "id" => Some(StixValue::String(self.id.clone())),
219 "risk" => Some(StixValue::Integer(self.risk)),
220 _ => None,
221 }
222 }
223 }
224
225 fn widget(risk: i64) -> StixObject {
226 StixObject::Custom(Arc::new(Widget {
227 type_: "x-widget".into(),
228 id: "x-widget--1".into(),
229 risk,
230 }))
231 }
232
233 #[test]
234 fn custom_exposes_object_view() {
235 let o = widget(90);
236 assert_eq!(o.type_(), Some("x-widget"));
237 assert_eq!(o.id(), Some("x-widget--1"));
238 assert_eq!(o.property("risk"), Some(StixValue::Integer(90)));
239 assert_eq!(o.property("missing"), None);
240 }
241
242 #[test]
243 fn custom_downcasts_to_concrete_type() {
244 let o = widget(90);
245 let w = o.downcast_ref::<Widget>().expect("downcast");
246 assert_eq!(w.risk, 90);
247 assert!(o.downcast_ref::<String>().is_none());
249 let generic = StixObject::from_json(serde_json::json!({"type":"x","id":"x--1"})).unwrap();
251 assert!(generic.downcast_ref::<Widget>().is_none());
252 }
253
254 #[test]
255 fn custom_clone_and_eq_and_serialize() {
256 assert_eq!(widget(90), widget(90));
257 assert_ne!(widget(90), widget(10));
258 let json = serde_json::to_value(widget(90)).unwrap();
259 assert_eq!(json["risk"], serde_json::json!(90));
260 }
261}