1use std::collections::BTreeMap;
4
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
13#[serde(untagged)]
14pub enum StixValue {
15 Null,
17 Bool(bool),
19 Integer(i64),
21 Float(f64),
23 String(String),
25 List(Vec<StixValue>),
27 Object(BTreeMap<String, StixValue>),
29}
30
31impl StixValue {
32 pub fn as_str(&self) -> Option<&str> {
34 match self {
35 StixValue::String(s) => Some(s),
36 _ => None,
37 }
38 }
39
40 pub fn as_i64(&self) -> Option<i64> {
42 match self {
43 StixValue::Integer(n) => Some(*n),
44 _ => None,
45 }
46 }
47
48 pub fn as_f64(&self) -> Option<f64> {
50 match self {
51 StixValue::Float(f) => Some(*f),
52 StixValue::Integer(n) => Some(*n as f64),
53 _ => None,
54 }
55 }
56
57 pub fn as_bool(&self) -> Option<bool> {
59 match self {
60 StixValue::Bool(b) => Some(*b),
61 _ => None,
62 }
63 }
64
65 pub fn as_list(&self) -> Option<&[StixValue]> {
67 match self {
68 StixValue::List(items) => Some(items),
69 _ => None,
70 }
71 }
72
73 pub fn as_object(&self) -> Option<&BTreeMap<String, StixValue>> {
75 match self {
76 StixValue::Object(map) => Some(map),
77 _ => None,
78 }
79 }
80
81 pub fn is_null(&self) -> bool {
83 matches!(self, StixValue::Null)
84 }
85}
86
87impl From<serde_json::Value> for StixValue {
88 fn from(v: serde_json::Value) -> Self {
89 use serde_json::Value;
90 match v {
91 Value::Null => StixValue::Null,
92 Value::Bool(b) => StixValue::Bool(b),
93 Value::Number(n) => {
94 if let Some(i) = n.as_i64() {
95 StixValue::Integer(i)
96 } else {
97 StixValue::Float(n.as_f64().unwrap_or(0.0))
99 }
100 }
101 Value::String(s) => StixValue::String(s),
102 Value::Array(arr) => StixValue::List(arr.into_iter().map(StixValue::from).collect()),
103 Value::Object(obj) => StixValue::Object(
104 obj.into_iter()
105 .map(|(k, v)| (k, StixValue::from(v)))
106 .collect(),
107 ),
108 }
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 #[test]
117 fn from_json_scalars() {
118 assert_eq!(StixValue::from(serde_json::json!(null)), StixValue::Null);
119 assert_eq!(
120 StixValue::from(serde_json::json!(true)),
121 StixValue::Bool(true)
122 );
123 assert_eq!(
124 StixValue::from(serde_json::json!(42)),
125 StixValue::Integer(42)
126 );
127 assert_eq!(
128 StixValue::from(serde_json::json!(-7)),
129 StixValue::Integer(-7)
130 );
131 assert_eq!(
132 StixValue::from(serde_json::json!(2.5)),
133 StixValue::Float(2.5)
134 );
135 assert_eq!(
136 StixValue::from(serde_json::json!("hi")),
137 StixValue::String("hi".to_string())
138 );
139 }
140
141 #[test]
142 fn from_json_nested() {
143 let v = StixValue::from(serde_json::json!({"a": [1, "x"], "b": true}));
144 match v {
145 StixValue::Object(map) => {
146 assert_eq!(
147 map.get("a"),
148 Some(&StixValue::List(vec![
149 StixValue::Integer(1),
150 StixValue::String("x".to_string())
151 ]))
152 );
153 assert_eq!(map.get("b"), Some(&StixValue::Bool(true)));
154 }
155 _ => panic!("expected object"),
156 }
157 }
158
159 #[test]
160 fn accessors() {
161 assert_eq!(StixValue::String("s".into()).as_str(), Some("s"));
162 assert_eq!(StixValue::Integer(3).as_i64(), Some(3));
163 assert_eq!(StixValue::Float(1.5).as_f64(), Some(1.5));
164 assert_eq!(StixValue::Integer(3).as_f64(), Some(3.0));
165 assert_eq!(StixValue::Bool(true).as_bool(), Some(true));
166 assert!(StixValue::Null.is_null());
167 assert_eq!(StixValue::String("s".into()).as_i64(), None);
168 }
169}