1use std::collections::BTreeMap;
7use std::fs::File;
8use std::io::{Read, Write};
9use std::path::Path;
10
11use serde::de::Error as _;
12use serde::de::{MapAccess, SeqAccess, Visitor};
13use serde::ser::{SerializeMap, SerializeSeq};
14use serde::{Deserialize, Deserializer, Serialize, Serializer};
15
16#[derive(Debug, Clone, PartialEq)]
34pub enum NxValue {
35 Bool(bool),
36 Int32(i32),
37 Int(i64),
38 Float32(f32),
39 Float(f64),
40 String(String),
46 Null,
48 Array(Vec<NxValue>),
49 Record {
53 type_name: Option<String>,
54 properties: BTreeMap<String, NxValue>,
55 },
56}
57
58impl NxValue {
59 pub fn empty() -> Self {
61 NxValue::Array(Vec::new())
62 }
63
64 pub fn is_empty_value(&self) -> bool {
66 match self {
67 NxValue::Null => true,
68 NxValue::Array(elements) => elements.is_empty(),
69 _ => false,
70 }
71 }
72
73 pub fn from_json_str(source: &str) -> Result<Self, serde_json::Error> {
75 serde_json::from_str(source)
76 }
77
78 pub fn from_json_reader<R: Read>(reader: R) -> Result<Self, serde_json::Error> {
80 serde_json::from_reader(reader)
81 }
82
83 pub fn from_json_file(path: impl AsRef<Path>) -> Result<Self, NxValueIoError> {
85 let file = File::open(path.as_ref())?;
86 Ok(Self::from_json_reader(file)?)
87 }
88
89 pub fn to_json_string(&self) -> Result<String, serde_json::Error> {
91 serde_json::to_string(self)
92 }
93
94 pub fn to_json_string_pretty(&self) -> Result<String, serde_json::Error> {
96 serde_json::to_string_pretty(self)
97 }
98
99 pub fn to_json_writer<W: Write>(&self, writer: W) -> Result<(), serde_json::Error> {
101 serde_json::to_writer(writer, self)
102 }
103
104 pub fn to_json_file(&self, path: impl AsRef<Path>) -> Result<(), NxValueIoError> {
106 let mut file = File::create(path.as_ref())?;
107 self.to_json_writer(&mut file)?;
108 file.flush()?;
109 Ok(())
110 }
111
112 pub fn to_json_file_pretty(&self, path: impl AsRef<Path>) -> Result<(), NxValueIoError> {
114 let mut file = File::create(path.as_ref())?;
115 let json = self.to_json_string_pretty()?;
116 file.write_all(json.as_bytes())?;
117 file.flush()?;
118 Ok(())
119 }
120
121 pub fn to_msgpack_vec(&self) -> Result<Vec<u8>, rmp_serde::encode::Error> {
123 rmp_serde::to_vec(self)
124 }
125
126 pub fn from_msgpack_slice(bytes: &[u8]) -> Result<Self, rmp_serde::decode::Error> {
128 rmp_serde::from_slice(bytes)
129 }
130}
131
132impl Serialize for NxValue {
133 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
134 match self {
135 NxValue::Bool(value) => serializer.serialize_bool(*value),
136 NxValue::Int32(value) => serializer.serialize_i32(*value),
137 NxValue::Int(value) => serializer.serialize_i64(*value),
138 NxValue::Float32(value) => serializer.serialize_f32(*value),
139 NxValue::Float(value) => serializer.serialize_f64(*value),
140 NxValue::String(value) => serializer.serialize_str(value),
141 NxValue::Null => serializer.serialize_unit(),
142 NxValue::Array(elements) => {
143 let mut seq = serializer.serialize_seq(Some(elements.len()))?;
144 for element in elements {
145 seq.serialize_element(element)?;
146 }
147 seq.end()
148 }
149 NxValue::Record {
150 type_name,
151 properties,
152 } => {
153 let len = properties.len() + usize::from(type_name.is_some());
154 let mut map = serializer.serialize_map(Some(len))?;
155
156 if let Some(type_name) = type_name {
157 map.serialize_entry("$type", type_name)?;
158 }
159
160 for (key, value) in properties {
161 map.serialize_entry(key, value)?;
162 }
163
164 map.end()
165 }
166 }
167 }
168}
169
170impl<'de> Deserialize<'de> for NxValue {
171 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
172 struct NxValueVisitor;
173
174 impl<'de> Visitor<'de> for NxValueVisitor {
175 type Value = NxValue;
176
177 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
178 formatter.write_str("a JSON-like value")
179 }
180
181 fn visit_unit<E: serde::de::Error>(self) -> Result<Self::Value, E> {
182 Ok(NxValue::Null)
183 }
184
185 fn visit_none<E: serde::de::Error>(self) -> Result<Self::Value, E> {
186 Ok(NxValue::Null)
187 }
188
189 fn visit_bool<E: serde::de::Error>(self, v: bool) -> Result<Self::Value, E> {
190 Ok(NxValue::Bool(v))
191 }
192
193 fn visit_i32<E: serde::de::Error>(self, v: i32) -> Result<Self::Value, E> {
194 Ok(NxValue::Int32(v))
195 }
196
197 fn visit_i64<E: serde::de::Error>(self, v: i64) -> Result<Self::Value, E> {
198 Ok(NxValue::Int(v))
199 }
200
201 fn visit_u64<E: serde::de::Error>(self, v: u64) -> Result<Self::Value, E> {
202 if v <= i64::MAX as u64 {
203 Ok(NxValue::Int(v as i64))
204 } else {
205 Ok(NxValue::Float(v as f64))
206 }
207 }
208
209 fn visit_f32<E: serde::de::Error>(self, v: f32) -> Result<Self::Value, E> {
210 Ok(NxValue::Float32(v))
211 }
212
213 fn visit_f64<E: serde::de::Error>(self, v: f64) -> Result<Self::Value, E> {
214 Ok(NxValue::Float(v))
215 }
216
217 fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
218 Ok(NxValue::String(v.to_owned()))
219 }
220
221 fn visit_string<E: serde::de::Error>(self, v: String) -> Result<Self::Value, E> {
222 Ok(NxValue::String(v))
223 }
224
225 fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
226 let mut values = Vec::new();
227 while let Some(value) = seq.next_element::<NxValue>()? {
228 values.push(value);
229 }
230 Ok(NxValue::Array(values))
231 }
232
233 fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> {
234 let mut type_name = None;
235 let mut properties = BTreeMap::new();
236 while let Some((key, value)) = map.next_entry::<String, NxValue>()? {
237 if key == "$type" {
238 match value {
239 NxValue::String(name) => {
240 type_name = Some(name);
241 }
242 other => {
243 return Err(A::Error::custom(format!(
244 "expected \"$type\" to be a string, got {:?}",
245 other
246 )));
247 }
248 }
249 continue;
250 }
251
252 properties.insert(key, value);
253 }
254
255 Ok(NxValue::Record {
256 type_name,
257 properties,
258 })
259 }
260 }
261
262 deserializer.deserialize_any(NxValueVisitor)
263 }
264}
265
266#[derive(Debug)]
268pub enum NxValueIoError {
269 Io(std::io::Error),
270 Json(serde_json::Error),
271}
272
273impl std::fmt::Display for NxValueIoError {
274 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
275 match self {
276 NxValueIoError::Io(e) => write!(f, "io error: {}", e),
277 NxValueIoError::Json(e) => write!(f, "json error: {}", e),
278 }
279 }
280}
281
282impl std::error::Error for NxValueIoError {
283 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
284 match self {
285 NxValueIoError::Io(e) => Some(e),
286 NxValueIoError::Json(e) => Some(e),
287 }
288 }
289}
290
291impl From<std::io::Error> for NxValueIoError {
292 fn from(value: std::io::Error) -> Self {
293 NxValueIoError::Io(value)
294 }
295}
296
297impl From<serde_json::Error> for NxValueIoError {
298 fn from(value: serde_json::Error) -> Self {
299 NxValueIoError::Json(value)
300 }
301}
302
303#[cfg(doctest)]
305#[doc = include_str!("../README.md")]
306struct ReadmeDoctests;
307
308#[cfg(test)]
309mod tests {
310 use super::*;
311 use std::io::Cursor;
312
313 #[test]
314 fn json_round_trip_in_memory() {
315 let mut obj = BTreeMap::new();
316 obj.insert("a".to_string(), NxValue::Int(1));
317 obj.insert("b".to_string(), NxValue::Bool(true));
318 obj.insert(
319 "c".to_string(),
320 NxValue::Array(vec![NxValue::Int(2), NxValue::String("x".to_string())]),
321 );
322
323 let value = NxValue::Record {
324 type_name: None,
325 properties: obj,
326 };
327 let json = value.to_json_string().unwrap();
328 let decoded = NxValue::from_json_str(&json).unwrap();
329 assert_eq!(decoded, value);
330 }
331
332 #[test]
333 fn json_round_trip_primitives() {
334 let cases = [
335 ("true", NxValue::Bool(true)),
336 ("false", NxValue::Bool(false)),
337 ("0", NxValue::Int(0)),
338 ("-1", NxValue::Int(-1)),
339 ("2.75", NxValue::Float(2.75)),
340 ("\"hello\"", NxValue::String("hello".to_string())),
341 ("[]", NxValue::Array(vec![])),
342 (
343 "{}",
344 NxValue::Record {
345 type_name: None,
346 properties: BTreeMap::new(),
347 },
348 ),
349 ];
350
351 for (json, expected) in cases {
352 let decoded = NxValue::from_json_str(json).unwrap();
353 assert_eq!(decoded, expected);
354 let encoded = decoded.to_json_string().unwrap();
355 let decoded_again = NxValue::from_json_str(&encoded).unwrap();
356 assert_eq!(decoded_again, expected);
357 }
358 }
359
360 #[test]
361 fn json_round_trip_nested_structures() {
362 let value = NxValue::Record {
363 type_name: None,
364 properties: BTreeMap::from([
365 (
366 "a".to_string(),
367 NxValue::Array(vec![NxValue::Int(1), NxValue::Int(2)]),
368 ),
369 (
370 "b".to_string(),
371 NxValue::Record {
372 type_name: None,
373 properties: BTreeMap::from([
374 ("x".to_string(), NxValue::Bool(false)),
375 ("y".to_string(), NxValue::empty()),
376 ]),
377 },
378 ),
379 ]),
380 };
381
382 let json = value.to_json_string().unwrap();
383 let decoded = NxValue::from_json_str(&json).unwrap();
384 assert_eq!(decoded, value);
385 }
386
387 #[test]
391 fn json_encoding_of_occurrence_typed_values() {
392 assert_eq!(
394 NxValue::Array(vec![NxValue::Int(1), NxValue::Int(2)])
395 .to_json_string()
396 .unwrap(),
397 "[1,2]"
398 );
399 assert_eq!(NxValue::empty().to_json_string().unwrap(), "[]");
400 assert_eq!(NxValue::Int(1).to_json_string().unwrap(), "1");
402 assert_eq!(NxValue::Null.to_json_string().unwrap(), "null");
403 assert_eq!(NxValue::from_json_str("null").unwrap(), NxValue::Null);
405 assert_eq!(NxValue::from_json_str("[]").unwrap(), NxValue::empty());
406 assert!(NxValue::from_json_str("null").unwrap().is_empty_value());
407 let book = NxValue::from_json_str(r#"{"$type":"Book","title":"A"}"#).unwrap();
409 let NxValue::Record { properties, .. } = &book else {
410 panic!("a record");
411 };
412 assert!(!properties.contains_key("author"));
413 assert_eq!(
414 book.to_json_string().unwrap(),
415 r#"{"$type":"Book","title":"A"}"#
416 );
417 let cleared = NxValue::Record {
419 type_name: Some("Book.Update".to_string()),
420 properties: BTreeMap::from([("author".to_string(), NxValue::Null)]),
421 };
422 assert_eq!(
423 cleared.to_json_string().unwrap(),
424 r#"{"$type":"Book.Update","author":null}"#
425 );
426 assert_eq!(
427 NxValue::from_json_str(r#"{"$type":"Book.Update","author":null}"#).unwrap(),
428 cleared
429 );
430 let plain = NxValue::Record {
432 type_name: Some("Box".to_string()),
433 properties: BTreeMap::from([("items".to_string(), NxValue::empty())]),
434 };
435 assert_eq!(
436 plain.to_json_string().unwrap(),
437 r#"{"$type":"Box","items":[]}"#
438 );
439 }
440
441 #[test]
442 fn msgpack_writes_null_as_nil() {
443 let bytes = NxValue::Null.to_msgpack_vec().unwrap();
444 assert_eq!(bytes, vec![0xc0]);
445 assert_eq!(NxValue::from_msgpack_slice(&bytes).unwrap(), NxValue::Null);
446 }
447
448 #[test]
449 fn json_round_trip_constant_case_as_bare_string() {
450 let value = NxValue::String("active".to_string());
451
452 let json = value.to_json_string().unwrap();
453 assert_eq!(json, "\"active\"");
454 let decoded = NxValue::from_json_str(&json).unwrap();
455 assert_eq!(decoded, value);
456 }
457
458 #[test]
459 fn json_parses_large_u64_as_float() {
460 let value = NxValue::from_json_str("18446744073709551615").unwrap();
461 assert!(matches!(value, NxValue::Float(_)));
462 }
463
464 #[test]
465 fn json_number_boundaries() {
466 let max_i64 = NxValue::from_json_str("9223372036854775807").unwrap();
467 assert_eq!(max_i64, NxValue::Int(i64::MAX));
468
469 let min_i64 = NxValue::from_json_str("-9223372036854775808").unwrap();
470 assert_eq!(min_i64, NxValue::Int(i64::MIN));
471
472 let just_over_max_i64 = NxValue::from_json_str("9223372036854775808").unwrap();
473 assert!(matches!(just_over_max_i64, NxValue::Float(_)));
474 }
475
476 #[test]
477 fn json_supports_exponent_notation() {
478 let value = NxValue::from_json_str("1e3").unwrap();
479 assert_eq!(value, NxValue::Float(1000.0));
480 }
481
482 #[test]
483 fn json_preserves_negative_zero_sign() {
484 let value = NxValue::from_json_str("-0.0").unwrap();
485 match value {
486 NxValue::Float(f) => {
487 assert_eq!(f.to_bits(), (-0.0f64).to_bits());
488 }
489 _ => panic!("Expected float"),
490 }
491 }
492
493 #[test]
494 fn json_object_serialization_is_deterministic() {
495 let mut obj = BTreeMap::new();
496 obj.insert("b".to_string(), NxValue::Int(2));
497 obj.insert("a".to_string(), NxValue::Int(1));
498
499 let value = NxValue::Record {
500 type_name: None,
501 properties: obj,
502 };
503 let json = value.to_json_string().unwrap();
504 assert_eq!(json, "{\"a\":1,\"b\":2}");
505 }
506
507 #[test]
508 fn json_reader_writer_round_trip() {
509 let value = NxValue::Array(vec![
510 NxValue::String("x".to_string()),
511 NxValue::Int(1),
512 NxValue::Bool(false),
513 ]);
514
515 let mut buffer = Vec::new();
516 value.to_json_writer(&mut buffer).unwrap();
517
518 let decoded = NxValue::from_json_reader(Cursor::new(buffer)).unwrap();
519 assert_eq!(decoded, value);
520 }
521
522 #[test]
523 fn json_pretty_string_round_trip() {
524 let value = NxValue::Record {
525 type_name: None,
526 properties: BTreeMap::from([(
527 "a".to_string(),
528 NxValue::Array(vec![NxValue::Int(1), NxValue::Int(2)]),
529 )]),
530 };
531
532 let json = value.to_json_string_pretty().unwrap();
533 let decoded = NxValue::from_json_str(&json).unwrap();
534 assert_eq!(decoded, value);
535 }
536
537 #[test]
538 fn json_invalid_input_errors() {
539 assert!(NxValue::from_json_str("{]").is_err());
540 assert!(NxValue::from_json_str("[1,]").is_err());
541 }
542
543 #[test]
544 fn json_missing_file_errors() {
545 let tmp = tempfile::tempdir().unwrap();
546 let missing = tmp.path().join("missing.json");
547
548 let err = NxValue::from_json_file(&missing).unwrap_err();
549 assert!(matches!(err, NxValueIoError::Io(_)));
550 }
551
552 #[test]
553 fn json_file_helpers_round_trip() {
554 let tmp = tempfile::tempdir().unwrap();
555 let path = tmp.path().join("value.json");
556
557 let mut obj = BTreeMap::new();
558 obj.insert("name".to_string(), NxValue::String("Ada".to_string()));
559 obj.insert("age".to_string(), NxValue::Int(42));
560 let value = NxValue::Record {
561 type_name: None,
562 properties: obj,
563 };
564
565 value.to_json_file_pretty(&path).unwrap();
566 let decoded = NxValue::from_json_file(&path).unwrap();
567 assert_eq!(decoded, value);
568 }
569
570 #[test]
571 fn msgpack_round_trip_in_memory() {
572 let mut obj = BTreeMap::new();
573 obj.insert("name".to_string(), NxValue::String("Ada".to_string()));
574 obj.insert("age".to_string(), NxValue::Int(42));
575
576 let value = NxValue::Record {
577 type_name: Some("User".to_string()),
578 properties: obj,
579 };
580
581 let bytes = value.to_msgpack_vec().unwrap();
582 let decoded = NxValue::from_msgpack_slice(&bytes).unwrap();
583 assert_eq!(decoded, value);
584 }
585
586 #[test]
587 fn msgpack_round_trip_constant_case_as_bare_string() {
588 let value = NxValue::String("active".to_string());
589
590 let bytes = value.to_msgpack_vec().unwrap();
591 let decoded = NxValue::from_msgpack_slice(&bytes).unwrap();
592 assert_eq!(decoded, value);
593 }
594
595 #[test]
596 fn int32_round_trip_msgpack() {
597 let value = NxValue::Int32(42);
598 let bytes = value.to_msgpack_vec().unwrap();
599 let decoded = NxValue::from_msgpack_slice(&bytes).unwrap();
600 match decoded {
602 NxValue::Int32(n) => assert_eq!(n, 42),
603 NxValue::Int(n) => assert_eq!(n, 42),
604 other => panic!("Expected integer, got {:?}", other),
605 }
606 }
607
608 #[test]
609 fn float32_serializes_to_json() {
610 let value = NxValue::Float32(2.75);
611 let json = value.to_json_string().unwrap();
612 let decoded = NxValue::from_json_str(&json).unwrap();
614 assert!(matches!(decoded, NxValue::Float(_)));
616 }
617
618 #[test]
619 fn int32_serializes_to_json() {
620 let value = NxValue::Int32(42);
621 let json = value.to_json_string().unwrap();
622 assert_eq!(json, "42");
623 let decoded = NxValue::from_json_str(&json).unwrap();
625 assert_eq!(decoded, NxValue::Int(42));
626 }
627}