1mod cast;
2pub mod cast_predicate;
3mod collatable;
4#[cfg(feature = "wasm")]
5mod wasm;
6
7pub use cast::CastError;
8
9use ankurah_proto as proto;
10use serde::{Deserialize, Serialize};
11use std::fmt::Display;
12
13mod json_as_bytes {
16 use serde::{Deserialize, Deserializer, Serialize, Serializer};
17
18 pub fn serialize<S>(value: &serde_json::Value, serializer: S) -> Result<S::Ok, S::Error>
19 where S: Serializer {
20 let bytes = serde_json::to_vec(value).map_err(serde::ser::Error::custom)?;
21 bytes.serialize(serializer)
22 }
23
24 pub fn deserialize<'de, D>(deserializer: D) -> Result<serde_json::Value, D::Error>
25 where D: Deserializer<'de> {
26 let bytes: Vec<u8> = Vec::deserialize(deserializer)?;
27 serde_json::from_slice(&bytes).map_err(serde::de::Error::custom)
28 }
29}
30
31#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
32pub enum Value {
33 I16(i16),
35 I32(i32),
36 I64(i64),
37 F64(f64),
38
39 Bool(bool),
40 String(String),
41 EntityId(proto::EntityId),
42 Object(Vec<u8>),
43 Binary(Vec<u8>),
44 #[serde(with = "json_as_bytes")]
47 Json(serde_json::Value),
48}
49
50impl Value {
51 pub fn json<T: Serialize>(value: &T) -> Result<Self, serde_json::Error> { Ok(Value::Json(serde_json::to_value(value)?)) }
53
54 pub fn parse_as_json<T: serde::de::DeserializeOwned>(&self) -> Result<T, crate::property::PropertyError> {
58 match self {
59 Value::Json(json) => Ok(serde_json::from_value(json.clone())?),
60 Value::Object(bytes) | Value::Binary(bytes) => Ok(serde_json::from_slice(bytes)?),
61 Value::String(s) => Ok(serde_json::from_str(s)?),
62 other => {
63 Err(crate::property::PropertyError::InvalidVariant { given: other.clone(), ty: std::any::type_name::<T>().to_string() })
64 }
65 }
66 }
67
68 pub fn parse_as_string<T: std::str::FromStr>(&self) -> Result<T, crate::property::PropertyError> {
72 match self {
73 Value::String(s) => s
74 .parse()
75 .map_err(|_| crate::property::PropertyError::InvalidValue { value: s.clone(), ty: std::any::type_name::<T>().to_string() }),
76 other => {
77 Err(crate::property::PropertyError::InvalidVariant { given: other.clone(), ty: std::any::type_name::<T>().to_string() })
78 }
79 }
80 }
81
82 pub fn extract_at_path(&self, path: &[String]) -> Option<Value> {
87 if path.is_empty() {
88 return Some(self.clone());
89 }
90
91 match self {
92 Value::Json(json) => {
93 let mut current = json;
94 for key in path {
95 current = current.get(key)?;
96 }
97 Some(json_value_to_value(current))
98 }
99 Value::Binary(bytes) => {
100 let json: serde_json::Value = serde_json::from_slice(bytes).ok()?;
101 let mut current = &json;
102 for key in path {
103 current = current.get(key)?;
104 }
105 Some(json_value_to_value(current))
106 }
107 Value::String(s) => {
108 let json: serde_json::Value = serde_json::from_str(s).ok()?;
109 let mut current = &json;
110 for key in path {
111 current = current.get(key)?;
112 }
113 Some(json_value_to_value(current))
114 }
115 _ => None,
116 }
117 }
118}
119
120fn json_value_to_value(json: &serde_json::Value) -> Value {
122 match json {
123 serde_json::Value::Null => Value::Json(serde_json::Value::Null),
124 serde_json::Value::Bool(b) => Value::Bool(*b),
125 serde_json::Value::Number(n) => {
126 if let Some(i) = n.as_i64() {
127 Value::I64(i)
128 } else if let Some(f) = n.as_f64() {
129 Value::F64(f)
130 } else {
131 Value::String(n.to_string())
132 }
133 }
134 serde_json::Value::String(s) => Value::String(s.clone()),
135 serde_json::Value::Array(_) | serde_json::Value::Object(_) => Value::Json(json.clone()),
137 }
138}
139
140#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
141pub enum ValueType {
142 I16,
143 I32,
144 I64,
145 F64,
146 Bool,
147 String,
148 EntityId,
149 Object,
150 Binary,
151 Json,
152}
153
154impl ValueType {
155 pub fn of(v: &Value) -> Self {
156 match v {
157 Value::I16(_) => ValueType::I16,
158 Value::I32(_) => ValueType::I32,
159 Value::I64(_) => ValueType::I64,
160 Value::F64(_) => ValueType::F64,
161 Value::Bool(_) => ValueType::Bool,
162 Value::String(_) => ValueType::String,
163 Value::EntityId(_) => ValueType::EntityId,
164 Value::Object(_) => ValueType::Object,
165 Value::Binary(_) => ValueType::Binary,
166 Value::Json(_) => ValueType::Json,
167 }
168 }
169}
170
171impl PartialOrd for Value {
172 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
173 match (self, other) {
174 (Value::I16(a), Value::I16(b)) => a.partial_cmp(b),
176 (Value::I32(a), Value::I32(b)) => a.partial_cmp(b),
177 (Value::I64(a), Value::I64(b)) => a.partial_cmp(b),
178 (Value::F64(a), Value::F64(b)) => a.partial_cmp(b),
179 (Value::Bool(a), Value::Bool(b)) => a.partial_cmp(b),
180 (Value::String(a), Value::String(b)) => a.partial_cmp(b),
181 (Value::EntityId(a), Value::EntityId(b)) => a.to_bytes().partial_cmp(&b.to_bytes()),
182 (Value::Object(a), Value::Object(b)) => a.partial_cmp(b),
183 (Value::Binary(a), Value::Binary(b)) => a.partial_cmp(b),
184 (Value::Json(a), Value::Json(b)) => a.to_string().partial_cmp(&b.to_string()),
186 _ => None,
188 }
189 }
190}
191
192impl Value {
194 pub fn gt(&self, other: &Self) -> bool { self.partial_cmp(other) == Some(std::cmp::Ordering::Greater) }
195
196 pub fn ge(&self, other: &Self) -> bool {
197 matches!(self.partial_cmp(other), Some(std::cmp::Ordering::Greater | std::cmp::Ordering::Equal))
198 }
199
200 pub fn lt(&self, other: &Self) -> bool { self.partial_cmp(other) == Some(std::cmp::Ordering::Less) }
201
202 pub fn le(&self, other: &Self) -> bool { matches!(self.partial_cmp(other), Some(std::cmp::Ordering::Less | std::cmp::Ordering::Equal)) }
203}
204
205impl Display for Value {
206 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
207 match self {
208 Value::I16(int) => write!(f, "{:?}", int),
209 Value::I32(int) => write!(f, "{:?}", int),
210 Value::I64(int) => write!(f, "{:?}", int),
211 Value::F64(float) => write!(f, "{:?}", float),
212 Value::Bool(bool) => write!(f, "{:?}", bool),
213 Value::String(string) => write!(f, "{:?}", string),
214 Value::EntityId(entity_id) => write!(f, "{}", entity_id),
215 Value::Object(object) => write!(f, "{:?}", object),
216 Value::Binary(binary) => write!(f, "{:?}", binary),
217 Value::Json(json) => write!(f, "{}", json),
218 }
219 }
220}
221
222impl From<ankql::ast::Literal> for Value {
223 fn from(literal: ankql::ast::Literal) -> Self {
224 match literal {
225 ankql::ast::Literal::I16(i) => Value::I16(i),
226 ankql::ast::Literal::I32(i) => Value::I32(i),
227 ankql::ast::Literal::I64(i) => Value::I64(i),
228 ankql::ast::Literal::F64(f) => Value::F64(f),
229 ankql::ast::Literal::Bool(b) => Value::Bool(b),
230 ankql::ast::Literal::String(s) => Value::String(s),
231 ankql::ast::Literal::EntityId(ulid) => Value::EntityId(proto::EntityId::from_ulid(ulid)),
232 ankql::ast::Literal::Object(object) => Value::Object(object),
233 ankql::ast::Literal::Binary(binary) => Value::Binary(binary),
234 ankql::ast::Literal::Json(json) => Value::Json(json),
235 }
236 }
237}
238
239impl From<&ankql::ast::Literal> for Value {
240 fn from(literal: &ankql::ast::Literal) -> Self {
241 match literal {
242 ankql::ast::Literal::I16(i) => Value::I16(*i),
243 ankql::ast::Literal::I32(i) => Value::I32(*i),
244 ankql::ast::Literal::I64(i) => Value::I64(*i),
245 ankql::ast::Literal::F64(f) => Value::F64(*f),
246 ankql::ast::Literal::Bool(b) => Value::Bool(*b),
247 ankql::ast::Literal::String(s) => Value::String(s.clone()),
248 ankql::ast::Literal::EntityId(ulid) => Value::EntityId(proto::EntityId::from_ulid(*ulid)),
249 ankql::ast::Literal::Object(object) => Value::Object(object.clone()),
250 ankql::ast::Literal::Binary(binary) => Value::Binary(binary.clone()),
251 ankql::ast::Literal::Json(json) => Value::Json(json.clone()),
252 }
253 }
254}
255
256impl From<Value> for ankql::ast::Literal {
257 fn from(value: Value) -> Self {
258 match value {
259 Value::I16(i) => ankql::ast::Literal::I16(i),
260 Value::I32(i) => ankql::ast::Literal::I32(i),
261 Value::I64(i) => ankql::ast::Literal::I64(i),
262 Value::F64(f) => ankql::ast::Literal::F64(f),
263 Value::Bool(b) => ankql::ast::Literal::Bool(b),
264 Value::String(s) => ankql::ast::Literal::String(s),
265 Value::EntityId(entity_id) => ankql::ast::Literal::EntityId(entity_id.to_ulid()),
266 Value::Object(bytes) => ankql::ast::Literal::String(String::from_utf8_lossy(&bytes).to_string()),
267 Value::Binary(bytes) => ankql::ast::Literal::String(String::from_utf8_lossy(&bytes).to_string()),
268 Value::Json(json) => ankql::ast::Literal::Json(json),
269 }
270 }
271}
272
273impl From<&Value> for ankql::ast::Literal {
274 fn from(value: &Value) -> Self {
275 match value {
276 Value::I16(i) => ankql::ast::Literal::I16(*i),
277 Value::I32(i) => ankql::ast::Literal::I32(*i),
278 Value::I64(i) => ankql::ast::Literal::I64(*i),
279 Value::F64(f) => ankql::ast::Literal::F64(*f),
280 Value::Bool(b) => ankql::ast::Literal::Bool(*b),
281 Value::String(s) => ankql::ast::Literal::String(s.clone()),
282 Value::EntityId(entity_id) => ankql::ast::Literal::EntityId(entity_id.to_ulid()),
283 Value::Object(bytes) => ankql::ast::Literal::String(String::from_utf8_lossy(bytes).to_string()),
284 Value::Binary(bytes) => ankql::ast::Literal::String(String::from_utf8_lossy(bytes).to_string()),
285 Value::Json(json) => ankql::ast::Literal::Json(json.clone()),
286 }
287 }
288}
289
290#[cfg(test)]
291mod tests {
292 use super::*;
293
294 #[test]
295 fn test_extract_at_path_empty() {
296 let value = Value::String("hello".to_string());
297 let result = value.extract_at_path(&[]);
298 assert_eq!(result, Some(Value::String("hello".to_string())));
299 }
300
301 #[test]
302 fn test_extract_at_path_json_string() {
303 let json = serde_json::json!({ "session_id": "sess123" });
304 let value = Value::Json(json);
305
306 let result = value.extract_at_path(&["session_id".to_string()]);
307 assert_eq!(result, Some(Value::String("sess123".to_string())));
308 }
309
310 #[test]
311 fn test_extract_at_path_json_number() {
312 let json = serde_json::json!({ "count": 42 });
313 let value = Value::Json(json);
314
315 let result = value.extract_at_path(&["count".to_string()]);
316 assert_eq!(result, Some(Value::I64(42)));
317 }
318
319 #[test]
320 fn test_extract_at_path_json_nested() {
321 let json = serde_json::json!({ "context": { "user": { "name": "Alice" } } });
322 let value = Value::Json(json);
323
324 let result = value.extract_at_path(&["context".to_string(), "user".to_string(), "name".to_string()]);
325 assert_eq!(result, Some(Value::String("Alice".to_string())));
326 }
327
328 #[test]
329 fn test_extract_at_path_missing() {
330 let json = serde_json::json!({ "session_id": "sess123" });
331 let value = Value::Json(json);
332
333 let result = value.extract_at_path(&["nonexistent".to_string()]);
334 assert_eq!(result, None);
335 }
336
337 #[test]
338 fn test_extract_at_path_non_json() {
339 let value = Value::String("not json".to_string());
340
341 let result = value.extract_at_path(&["field".to_string()]);
343 assert_eq!(result, None);
344 }
345}