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
308pub mod color;
309pub mod math;
310
311#[cfg(test)]
312mod tests {
313 use super::*;
314 use std::io::Cursor;
315
316 #[test]
317 fn json_round_trip_in_memory() {
318 let mut obj = BTreeMap::new();
319 obj.insert("a".to_string(), NxValue::Int(1));
320 obj.insert("b".to_string(), NxValue::Bool(true));
321 obj.insert(
322 "c".to_string(),
323 NxValue::Array(vec![NxValue::Int(2), NxValue::String("x".to_string())]),
324 );
325
326 let value = NxValue::Record {
327 type_name: None,
328 properties: obj,
329 };
330 let json = value.to_json_string().unwrap();
331 let decoded = NxValue::from_json_str(&json).unwrap();
332 assert_eq!(decoded, value);
333 }
334
335 #[test]
336 fn json_round_trip_primitives() {
337 let cases = [
338 ("true", NxValue::Bool(true)),
339 ("false", NxValue::Bool(false)),
340 ("0", NxValue::Int(0)),
341 ("-1", NxValue::Int(-1)),
342 ("2.75", NxValue::Float(2.75)),
343 ("\"hello\"", NxValue::String("hello".to_string())),
344 ("[]", NxValue::Array(vec![])),
345 (
346 "{}",
347 NxValue::Record {
348 type_name: None,
349 properties: BTreeMap::new(),
350 },
351 ),
352 ];
353
354 for (json, expected) in cases {
355 let decoded = NxValue::from_json_str(json).unwrap();
356 assert_eq!(decoded, expected);
357 let encoded = decoded.to_json_string().unwrap();
358 let decoded_again = NxValue::from_json_str(&encoded).unwrap();
359 assert_eq!(decoded_again, expected);
360 }
361 }
362
363 #[test]
364 fn json_round_trip_nested_structures() {
365 let value = NxValue::Record {
366 type_name: None,
367 properties: BTreeMap::from([
368 (
369 "a".to_string(),
370 NxValue::Array(vec![NxValue::Int(1), NxValue::Int(2)]),
371 ),
372 (
373 "b".to_string(),
374 NxValue::Record {
375 type_name: None,
376 properties: BTreeMap::from([
377 ("x".to_string(), NxValue::Bool(false)),
378 ("y".to_string(), NxValue::empty()),
379 ]),
380 },
381 ),
382 ]),
383 };
384
385 let json = value.to_json_string().unwrap();
386 let decoded = NxValue::from_json_str(&json).unwrap();
387 assert_eq!(decoded, value);
388 }
389
390 #[test]
394 fn json_encoding_of_occurrence_typed_values() {
395 assert_eq!(
397 NxValue::Array(vec![NxValue::Int(1), NxValue::Int(2)])
398 .to_json_string()
399 .unwrap(),
400 "[1,2]"
401 );
402 assert_eq!(NxValue::empty().to_json_string().unwrap(), "[]");
403 assert_eq!(NxValue::Int(1).to_json_string().unwrap(), "1");
405 assert_eq!(NxValue::Null.to_json_string().unwrap(), "null");
406 assert_eq!(NxValue::from_json_str("null").unwrap(), NxValue::Null);
408 assert_eq!(NxValue::from_json_str("[]").unwrap(), NxValue::empty());
409 assert!(NxValue::from_json_str("null").unwrap().is_empty_value());
410 let book = NxValue::from_json_str(r#"{"$type":"Book","title":"A"}"#).unwrap();
412 let NxValue::Record { properties, .. } = &book else {
413 panic!("a record");
414 };
415 assert!(!properties.contains_key("author"));
416 assert_eq!(
417 book.to_json_string().unwrap(),
418 r#"{"$type":"Book","title":"A"}"#
419 );
420 let cleared = NxValue::Record {
422 type_name: Some("Book.Update".to_string()),
423 properties: BTreeMap::from([("author".to_string(), NxValue::Null)]),
424 };
425 assert_eq!(
426 cleared.to_json_string().unwrap(),
427 r#"{"$type":"Book.Update","author":null}"#
428 );
429 assert_eq!(
430 NxValue::from_json_str(r#"{"$type":"Book.Update","author":null}"#).unwrap(),
431 cleared
432 );
433 let plain = NxValue::Record {
435 type_name: Some("Box".to_string()),
436 properties: BTreeMap::from([("items".to_string(), NxValue::empty())]),
437 };
438 assert_eq!(
439 plain.to_json_string().unwrap(),
440 r#"{"$type":"Box","items":[]}"#
441 );
442 }
443
444 #[test]
445 fn msgpack_writes_null_as_nil() {
446 let bytes = NxValue::Null.to_msgpack_vec().unwrap();
447 assert_eq!(bytes, vec![0xc0]);
448 assert_eq!(NxValue::from_msgpack_slice(&bytes).unwrap(), NxValue::Null);
449 }
450
451 #[test]
452 fn json_round_trip_constant_case_as_bare_string() {
453 let value = NxValue::String("active".to_string());
454
455 let json = value.to_json_string().unwrap();
456 assert_eq!(json, "\"active\"");
457 let decoded = NxValue::from_json_str(&json).unwrap();
458 assert_eq!(decoded, value);
459 }
460
461 #[test]
462 fn json_parses_large_u64_as_float() {
463 let value = NxValue::from_json_str("18446744073709551615").unwrap();
464 assert!(matches!(value, NxValue::Float(_)));
465 }
466
467 #[test]
468 fn json_number_boundaries() {
469 let max_i64 = NxValue::from_json_str("9223372036854775807").unwrap();
470 assert_eq!(max_i64, NxValue::Int(i64::MAX));
471
472 let min_i64 = NxValue::from_json_str("-9223372036854775808").unwrap();
473 assert_eq!(min_i64, NxValue::Int(i64::MIN));
474
475 let just_over_max_i64 = NxValue::from_json_str("9223372036854775808").unwrap();
476 assert!(matches!(just_over_max_i64, NxValue::Float(_)));
477 }
478
479 #[test]
480 fn json_supports_exponent_notation() {
481 let value = NxValue::from_json_str("1e3").unwrap();
482 assert_eq!(value, NxValue::Float(1000.0));
483 }
484
485 #[test]
486 fn json_preserves_negative_zero_sign() {
487 let value = NxValue::from_json_str("-0.0").unwrap();
488 match value {
489 NxValue::Float(f) => {
490 assert_eq!(f.to_bits(), (-0.0f64).to_bits());
491 }
492 _ => panic!("Expected float"),
493 }
494 }
495
496 #[test]
497 fn json_object_serialization_is_deterministic() {
498 let mut obj = BTreeMap::new();
499 obj.insert("b".to_string(), NxValue::Int(2));
500 obj.insert("a".to_string(), NxValue::Int(1));
501
502 let value = NxValue::Record {
503 type_name: None,
504 properties: obj,
505 };
506 let json = value.to_json_string().unwrap();
507 assert_eq!(json, "{\"a\":1,\"b\":2}");
508 }
509
510 #[test]
511 fn json_reader_writer_round_trip() {
512 let value = NxValue::Array(vec![
513 NxValue::String("x".to_string()),
514 NxValue::Int(1),
515 NxValue::Bool(false),
516 ]);
517
518 let mut buffer = Vec::new();
519 value.to_json_writer(&mut buffer).unwrap();
520
521 let decoded = NxValue::from_json_reader(Cursor::new(buffer)).unwrap();
522 assert_eq!(decoded, value);
523 }
524
525 #[test]
526 fn json_pretty_string_round_trip() {
527 let value = NxValue::Record {
528 type_name: None,
529 properties: BTreeMap::from([(
530 "a".to_string(),
531 NxValue::Array(vec![NxValue::Int(1), NxValue::Int(2)]),
532 )]),
533 };
534
535 let json = value.to_json_string_pretty().unwrap();
536 let decoded = NxValue::from_json_str(&json).unwrap();
537 assert_eq!(decoded, value);
538 }
539
540 #[test]
541 fn json_invalid_input_errors() {
542 assert!(NxValue::from_json_str("{]").is_err());
543 assert!(NxValue::from_json_str("[1,]").is_err());
544 }
545
546 #[test]
547 fn json_missing_file_errors() {
548 let tmp = tempfile::tempdir().unwrap();
549 let missing = tmp.path().join("missing.json");
550
551 let err = NxValue::from_json_file(&missing).unwrap_err();
552 assert!(matches!(err, NxValueIoError::Io(_)));
553 }
554
555 #[test]
556 fn json_file_helpers_round_trip() {
557 let tmp = tempfile::tempdir().unwrap();
558 let path = tmp.path().join("value.json");
559
560 let mut obj = BTreeMap::new();
561 obj.insert("name".to_string(), NxValue::String("Ada".to_string()));
562 obj.insert("age".to_string(), NxValue::Int(42));
563 let value = NxValue::Record {
564 type_name: None,
565 properties: obj,
566 };
567
568 value.to_json_file_pretty(&path).unwrap();
569 let decoded = NxValue::from_json_file(&path).unwrap();
570 assert_eq!(decoded, value);
571 }
572
573 #[test]
574 fn msgpack_round_trip_in_memory() {
575 let mut obj = BTreeMap::new();
576 obj.insert("name".to_string(), NxValue::String("Ada".to_string()));
577 obj.insert("age".to_string(), NxValue::Int(42));
578
579 let value = NxValue::Record {
580 type_name: Some("User".to_string()),
581 properties: obj,
582 };
583
584 let bytes = value.to_msgpack_vec().unwrap();
585 let decoded = NxValue::from_msgpack_slice(&bytes).unwrap();
586 assert_eq!(decoded, value);
587 }
588
589 #[test]
590 fn msgpack_round_trip_constant_case_as_bare_string() {
591 let value = NxValue::String("active".to_string());
592
593 let bytes = value.to_msgpack_vec().unwrap();
594 let decoded = NxValue::from_msgpack_slice(&bytes).unwrap();
595 assert_eq!(decoded, value);
596 }
597
598 #[test]
599 fn int32_round_trip_msgpack() {
600 let value = NxValue::Int32(42);
601 let bytes = value.to_msgpack_vec().unwrap();
602 let decoded = NxValue::from_msgpack_slice(&bytes).unwrap();
603 match decoded {
605 NxValue::Int32(n) => assert_eq!(n, 42),
606 NxValue::Int(n) => assert_eq!(n, 42),
607 other => panic!("Expected integer, got {:?}", other),
608 }
609 }
610
611 #[test]
612 fn float32_serializes_to_json() {
613 let value = NxValue::Float32(2.75);
614 let json = value.to_json_string().unwrap();
615 let decoded = NxValue::from_json_str(&json).unwrap();
617 assert!(matches!(decoded, NxValue::Float(_)));
619 }
620
621 #[test]
622 fn int32_serializes_to_json() {
623 let value = NxValue::Int32(42);
624 let json = value.to_json_string().unwrap();
625 assert_eq!(json, "42");
626 let decoded = NxValue::from_json_str(&json).unwrap();
628 assert_eq!(decoded, NxValue::Int(42));
629 }
630}
631
632#[cfg(test)]
633mod color_corpus_tests;