1use std::collections::BTreeMap;
28use std::fmt;
29
30use num_bigint::BigInt;
31use num_rational::BigRational;
32use num_traits::Zero;
33use serde::de::Error as DeError;
34use serde::{Deserialize, Deserializer, Serialize, Serializer};
35
36use crate::error::{EngineError, ErrorCode};
37use crate::limits::Limits;
38use crate::number::{Decimal, Float64, Number};
39
40#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
44pub struct Dimension {
45 exponents: [i8; 8],
48}
49
50pub const DIM_LENGTH: usize = 0;
51pub const DIM_MASS: usize = 1;
52pub const DIM_TIME: usize = 2;
53pub const DIM_CURRENT: usize = 3;
54pub const DIM_TEMPERATURE: usize = 4;
55pub const DIM_AMOUNT: usize = 5;
56pub const DIM_LUMINOUS: usize = 6;
57pub const DIM_ANGLE: usize = 7;
58
59pub const DIMENSION_NAMES: [&str; 8] = [
60 "length",
61 "mass",
62 "time",
63 "current",
64 "temperature",
65 "amount",
66 "luminous",
67 "angle",
68];
69
70impl Dimension {
71 pub const DIMENSIONLESS: Dimension = Dimension { exponents: [0; 8] };
72 pub const ANGLE: Dimension = Dimension {
73 exponents: [0, 0, 0, 0, 0, 0, 0, 1],
74 };
75
76 pub fn new(exponents: [i8; 8]) -> Dimension {
77 Dimension { exponents }
78 }
79
80 pub fn exponents(&self) -> [i8; 8] {
81 self.exponents
82 }
83
84 pub fn get(&self, index: usize) -> i8 {
85 self.exponents[index]
86 }
87
88 pub fn is_dimensionless(&self) -> bool {
89 self.exponents.iter().all(|e| *e == 0)
90 }
91
92 pub fn is_angle(&self) -> bool {
93 self.exponents[..7].iter().all(|e| *e == 0) && self.exponents[DIM_ANGLE] != 0
94 }
95
96 pub fn is_angle_only(&self) -> bool {
97 self.exponents[..7].iter().all(|e| *e == 0) && self.exponents[DIM_ANGLE] == 1
98 }
99
100 pub fn multiply(&self, other: &Dimension) -> Result<Dimension, EngineError> {
101 let mut out = [0i8; 8];
102 for (slot, (left, right)) in out
103 .iter_mut()
104 .zip(self.exponents.iter().zip(other.exponents.iter()))
105 {
106 let value = *left as i32 + *right as i32;
107 if value > i8::MAX as i32 || value < i8::MIN as i32 {
108 return Err(EngineError::new(
109 ErrorCode::DomainViolation,
110 "dimension exponent overflow",
111 ));
112 }
113 *slot = value as i8;
114 }
115 Ok(Dimension { exponents: out })
116 }
117
118 pub fn divide(&self, other: &Dimension) -> Result<Dimension, EngineError> {
119 let mut out = [0i8; 8];
120 for (slot, (left, right)) in out
121 .iter_mut()
122 .zip(self.exponents.iter().zip(other.exponents.iter()))
123 {
124 let value = *left as i32 - *right as i32;
125 if value > i8::MAX as i32 || value < i8::MIN as i32 {
126 return Err(EngineError::new(
127 ErrorCode::DomainViolation,
128 "dimension exponent overflow",
129 ));
130 }
131 *slot = value as i8;
132 }
133 Ok(Dimension { exponents: out })
134 }
135
136 pub fn pow(&self, exponent: i32) -> Result<Dimension, EngineError> {
137 let mut out = [0i8; 8];
138 for (slot, source) in out.iter_mut().zip(self.exponents.iter()) {
139 let value = *source as i32 * exponent;
140 if value > i8::MAX as i32 || value < i8::MIN as i32 {
141 return Err(EngineError::new(
142 ErrorCode::DomainViolation,
143 "dimension exponent overflow",
144 ));
145 }
146 *slot = value as i8;
147 }
148 Ok(Dimension { exponents: out })
149 }
150
151 pub fn to_json(&self) -> serde_json::Value {
152 let mut map = serde_json::Map::new();
153 for (i, name) in DIMENSION_NAMES.iter().enumerate() {
154 if self.exponents[i] != 0 {
155 map.insert(
156 (*name).to_string(),
157 serde_json::Value::from(self.exponents[i]),
158 );
159 }
160 }
161 serde_json::Value::Object(map)
162 }
163
164 pub fn from_json(value: &serde_json::Value) -> Result<Dimension, EngineError> {
165 let object = value
166 .as_object()
167 .ok_or_else(|| EngineError::malformed("dimension must be a JSON object"))?;
168 let mut exponents = [0i8; 8];
169 for (key, raw) in object {
170 let index = DIMENSION_NAMES
171 .iter()
172 .position(|name| name == key)
173 .ok_or_else(|| {
174 EngineError::malformed(format!("unknown dimension component {key:?}"))
175 })?;
176 let value = raw.as_i64().ok_or_else(|| {
177 EngineError::malformed(format!("dimension component {key:?} must be an integer"))
178 })?;
179 if !(-128..=127).contains(&value) {
180 return Err(EngineError::malformed(format!(
181 "dimension component {key:?} out of range"
182 )));
183 }
184 exponents[index] = value as i8;
185 }
186 Ok(Dimension { exponents })
187 }
188
189 pub fn symbol(&self) -> String {
191 const SYMBOLS: [&str; 8] = ["m", "kg", "s", "A", "K", "mol", "cd", "rad"];
192 if self.is_dimensionless() {
193 return "1".to_string();
194 }
195 let mut parts = Vec::new();
196 for (i, symbol) in SYMBOLS.iter().enumerate() {
197 match self.exponents[i] {
198 0 => {}
199 1 => parts.push((*symbol).to_string()),
200 e => parts.push(format!("{symbol}^{e}")),
201 }
202 }
203 parts.join("*")
204 }
205}
206
207impl Serialize for Dimension {
208 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
209 self.to_json().serialize(serializer)
210 }
211}
212
213impl<'de> Deserialize<'de> for Dimension {
214 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
215 let raw = serde_json::Value::deserialize(deserializer)?;
216 Dimension::from_json(&raw).map_err(D::Error::custom)
217 }
218}
219
220impl fmt::Display for Dimension {
221 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
222 f.write_str(&self.symbol())
223 }
224}
225
226#[derive(Clone, Debug, PartialEq)]
229pub enum Bound {
230 Unbounded,
231 Finite(Number),
232}
233
234#[derive(Clone, Debug, PartialEq)]
236pub enum Value {
237 Null,
238 Bool(bool),
239 Text(String),
240 Number(Number),
241 Array(Vec<Value>),
242 Record(BTreeMap<String, Value>),
243 Quantity {
244 value: Box<Value>,
245 dimension: Dimension,
246 },
247 Money {
248 amount: Box<Value>,
249 currency: String,
250 },
251 Matrix {
252 rows: u32,
253 cols: u32,
254 data: Vec<Value>,
255 },
256 Bound(Bound),
257}
258
259pub const RESERVED_KIND_KEY: &str = "kind";
261
262impl Value {
263 pub fn number(number: Number) -> Value {
264 Value::Number(number)
265 }
266
267 pub fn decimal(decimal: Decimal) -> Value {
268 Value::Number(Number::Decimal(decimal))
269 }
270
271 pub fn integer(value: BigInt) -> Value {
272 Value::Number(Number::Integer(value))
273 }
274
275 pub fn text(value: impl Into<String>) -> Value {
276 Value::Text(value.into())
277 }
278
279 pub fn record(entries: impl IntoIterator<Item = (impl Into<String>, Value)>) -> Value {
280 Value::Record(entries.into_iter().map(|(k, v)| (k.into(), v)).collect())
281 }
282
283 pub fn kind_name(&self) -> &'static str {
284 match self {
285 Value::Null => "null",
286 Value::Bool(_) => "boolean",
287 Value::Text(_) => "text",
288 Value::Number(n) => n.kind_name(),
289 Value::Array(_) => "array",
290 Value::Record(_) => "record",
291 Value::Quantity { .. } => "quantity",
292 Value::Money { .. } => "money",
293 Value::Matrix { .. } => "matrix",
294 Value::Bound(_) => "bound",
295 }
296 }
297
298 pub fn is_null(&self) -> bool {
299 matches!(self, Value::Null)
300 }
301
302 pub fn as_number(&self) -> Result<&Number, EngineError> {
303 match self {
304 Value::Number(n) => Ok(n),
305 other => Err(EngineError::malformed(format!(
306 "expected a number, found {}",
307 other.kind_name()
308 ))),
309 }
310 }
311
312 pub fn as_array(&self) -> Result<&[Value], EngineError> {
313 match self {
314 Value::Array(items) => Ok(items),
315 other => Err(EngineError::malformed(format!(
316 "expected an array, found {}",
317 other.kind_name()
318 ))),
319 }
320 }
321
322 pub fn as_record(&self) -> Result<&BTreeMap<String, Value>, EngineError> {
323 match self {
324 Value::Record(fields) => Ok(fields),
325 other => Err(EngineError::malformed(format!(
326 "expected a record, found {}",
327 other.kind_name()
328 ))),
329 }
330 }
331
332 pub fn as_text(&self) -> Result<&str, EngineError> {
333 match self {
334 Value::Text(text) => Ok(text),
335 other => Err(EngineError::malformed(format!(
336 "expected text, found {}",
337 other.kind_name()
338 ))),
339 }
340 }
341
342 pub fn as_bool(&self) -> Result<bool, EngineError> {
343 match self {
344 Value::Bool(value) => Ok(*value),
345 other => Err(EngineError::malformed(format!(
346 "expected a boolean, found {}",
347 other.kind_name()
348 ))),
349 }
350 }
351
352 pub fn as_money(&self) -> Result<(&Number, &str), EngineError> {
353 match self {
354 Value::Money { amount, currency } => Ok((amount.as_number()?, currency)),
355 other => Err(EngineError::malformed(format!(
356 "expected money, found {}",
357 other.kind_name()
358 ))),
359 }
360 }
361
362 pub fn as_quantity(&self) -> Result<(&Number, Dimension), EngineError> {
363 match self {
364 Value::Quantity { value, dimension } => Ok((value.as_number()?, *dimension)),
365 other => Err(EngineError::malformed(format!(
366 "expected a quantity, found {}",
367 other.kind_name()
368 ))),
369 }
370 }
371
372 pub fn check_limits(&self, limits: &Limits, depth: usize) -> Result<(), EngineError> {
374 if depth > limits.max_recursion_depth {
375 return Err(EngineError::new(
376 ErrorCode::ResourceLimit,
377 format!(
378 "value nesting depth exceeds the limit of {}",
379 limits.max_recursion_depth
380 ),
381 ));
382 }
383 match self {
384 Value::Array(items) => {
385 if items.len() > limits.max_array_len {
386 return Err(EngineError::new(
387 ErrorCode::ResourceLimit,
388 format!(
389 "array length {} exceeds the limit of {}",
390 items.len(),
391 limits.max_array_len
392 ),
393 ));
394 }
395 for item in items {
396 item.check_limits(limits, depth + 1)?;
397 }
398 }
399 Value::Record(fields) => {
400 for value in fields.values() {
401 value.check_limits(limits, depth + 1)?;
402 }
403 }
404 Value::Quantity { value, .. } => value.check_limits(limits, depth + 1)?,
405 Value::Money { amount, .. } => amount.check_limits(limits, depth + 1)?,
406 Value::Matrix { rows, cols, data } => {
407 if (*rows as usize).saturating_mul(*cols as usize) > limits.max_matrix_elements {
408 return Err(EngineError::new(
409 ErrorCode::ResourceLimit,
410 format!(
411 "matrix {}x{} exceeds the element limit of {}",
412 rows, cols, limits.max_matrix_elements
413 ),
414 ));
415 }
416 for item in data {
417 item.check_limits(limits, depth + 1)?;
418 }
419 }
420 _ => {}
421 }
422 Ok(())
423 }
424
425 pub fn from_json(raw: &serde_json::Value, limits: &Limits) -> Result<Value, EngineError> {
428 Value::from_json_at(raw, limits, 0)
429 }
430
431 fn from_json_at(
432 raw: &serde_json::Value,
433 limits: &Limits,
434 depth: usize,
435 ) -> Result<Value, EngineError> {
436 if depth > limits.max_recursion_depth {
437 return Err(EngineError::new(
438 ErrorCode::ResourceLimit,
439 format!(
440 "value nesting depth exceeds the limit of {}",
441 limits.max_recursion_depth
442 ),
443 ));
444 }
445 match raw {
446 serde_json::Value::Null => Ok(Value::Null),
447 serde_json::Value::Bool(value) => Ok(Value::Bool(*value)),
448 serde_json::Value::String(text) => {
449 if text.len() > limits.max_string_len {
450 return Err(EngineError::new(
451 ErrorCode::ResourceLimit,
452 format!(
453 "string length {} exceeds the limit of {}",
454 text.len(),
455 limits.max_string_len
456 ),
457 ));
458 }
459 Ok(Value::Text(text.clone()))
460 }
461 serde_json::Value::Number(number) => {
462 let text = number.to_string();
463 if text.contains('.') || text.contains('e') || text.contains('E') {
464 Ok(Value::Number(Number::Decimal(Decimal::parse(
465 &text, limits,
466 )?)))
467 } else {
468 let value = text.parse::<BigInt>().map_err(|_| {
469 EngineError::malformed(format!("invalid JSON integer {text:?}"))
470 })?;
471 if value.bits() > limits.max_integer_bits as u64 {
472 return Err(EngineError::new(
473 ErrorCode::ResourceLimit,
474 format!("integer needs {} bits, exceeding the limit", value.bits()),
475 ));
476 }
477 Ok(Value::Number(Number::Integer(value)))
478 }
479 }
480 serde_json::Value::Array(items) => {
481 if items.len() > limits.max_array_len {
482 return Err(EngineError::new(
483 ErrorCode::ResourceLimit,
484 format!(
485 "array length {} exceeds the limit of {}",
486 items.len(),
487 limits.max_array_len
488 ),
489 ));
490 }
491 let mut out = Vec::with_capacity(items.len());
492 for item in items {
493 out.push(Value::from_json_at(item, limits, depth + 1)?);
494 }
495 Ok(Value::Array(out))
496 }
497 serde_json::Value::Object(object) => {
498 if let Some(tagged) = parse_tagged(object, limits, depth)? {
499 return Ok(tagged);
500 }
501 let mut fields = BTreeMap::new();
502 for (key, value) in object {
503 if key == RESERVED_KIND_KEY {
504 return Err(EngineError::malformed(
505 "record key \"kind\" is reserved for tagged values",
506 ));
507 }
508 fields.insert(key.clone(), Value::from_json_at(value, limits, depth + 1)?);
509 }
510 Ok(Value::Record(fields))
511 }
512 }
513 }
514}
515
516fn parse_tagged(
517 object: &serde_json::Map<String, serde_json::Value>,
518 limits: &Limits,
519 depth: usize,
520) -> Result<Option<Value>, EngineError> {
521 let Some(kind) = object.get(RESERVED_KIND_KEY).and_then(|v| v.as_str()) else {
522 return Ok(None);
523 };
524 let number_from = |raw: &serde_json::Value| -> Result<Number, EngineError> {
525 Value::from_json_at(raw, limits, depth + 1)?
526 .as_number()
527 .cloned()
528 };
529 match kind {
530 "integer" => {
531 let raw = object
532 .get("value")
533 .ok_or_else(|| EngineError::malformed("integer value is missing"))?;
534 let text = match raw {
535 serde_json::Value::String(s) => s.clone(),
536 other => other.to_string(),
537 };
538 if !text
539 .strip_prefix(['+', '-'])
540 .unwrap_or(&text)
541 .bytes()
542 .all(|b| b.is_ascii_digit())
543 || text.is_empty()
544 || text.strip_prefix(['+', '-']).unwrap_or(&text).is_empty()
545 {
546 return Err(EngineError::malformed(format!(
547 "invalid integer payload {text:?}"
548 )));
549 }
550 let value = text
551 .parse::<BigInt>()
552 .map_err(|_| EngineError::malformed(format!("invalid integer payload {text:?}")))?;
553 if value.bits() > limits.max_integer_bits as u64 {
554 return Err(EngineError::new(
555 ErrorCode::ResourceLimit,
556 "integer payload exceeds the bit limit",
557 ));
558 }
559 Ok(Some(Value::Number(Number::Integer(value))))
560 }
561 "decimal" => {
562 let raw = object
563 .get("value")
564 .ok_or_else(|| EngineError::malformed("decimal value is missing"))?;
565 let text = match raw {
566 serde_json::Value::String(s) => s.clone(),
567 other => other.to_string(),
568 };
569 Ok(Some(Value::Number(Number::Decimal(Decimal::parse(
570 &text, limits,
571 )?))))
572 }
573 "rational" => {
574 let numer = object
575 .get("numerator")
576 .and_then(|v| v.as_str())
577 .ok_or_else(|| EngineError::malformed("rational numerator must be a string"))?;
578 let denom = object
579 .get("denominator")
580 .and_then(|v| v.as_str())
581 .ok_or_else(|| EngineError::malformed("rational denominator must be a string"))?;
582 let numer = numer
583 .parse::<BigInt>()
584 .map_err(|_| EngineError::malformed("invalid rational numerator"))?;
585 let denom = denom
586 .parse::<BigInt>()
587 .map_err(|_| EngineError::malformed("invalid rational denominator"))?;
588 if denom.is_zero() {
589 return Err(EngineError::division_by_zero(
590 "rational denominator must not be zero",
591 ));
592 }
593 Ok(Some(Value::Number(Number::Rational(BigRational::new(
594 numer, denom,
595 )))))
596 }
597 "float64" => {
598 let raw = object
599 .get("value")
600 .ok_or_else(|| EngineError::malformed("float64 value is missing"))?;
601 let text = match raw {
602 serde_json::Value::String(s) => s.clone(),
603 other => other.to_string(),
604 };
605 let value: f64 = text
606 .parse()
607 .map_err(|_| EngineError::malformed(format!("invalid float64 payload {text:?}")))?;
608 Ok(Some(Value::Number(Number::Float64(Float64::new(value)?))))
609 }
610 "quantity" => {
611 let raw = object
612 .get("value")
613 .ok_or_else(|| EngineError::malformed("quantity value is missing"))?;
614 let value = Value::from_json_at(raw, limits, depth + 1)?;
615 if !matches!(value, Value::Number(_)) {
616 return Err(EngineError::malformed(
617 "quantity value must be a scalar number",
618 ));
619 }
620 let dimension = match object.get("dimension") {
621 Some(raw) => Dimension::from_json(raw)?,
622 None => Dimension::DIMENSIONLESS,
623 };
624 Ok(Some(Value::Quantity {
625 value: Box::new(value),
626 dimension,
627 }))
628 }
629 "money" => {
630 let raw = object
631 .get("amount")
632 .ok_or_else(|| EngineError::malformed("money amount is missing"))?;
633 let amount = number_from(raw)?;
634 let currency = object
635 .get("currency")
636 .and_then(|v| v.as_str())
637 .ok_or_else(|| EngineError::malformed("money currency is missing"))?
638 .to_string();
639 validate_currency(¤cy)?;
640 Ok(Some(Value::Money {
641 amount: Box::new(Value::Number(amount)),
642 currency,
643 }))
644 }
645 "matrix" => {
646 let rows = object
647 .get("rows")
648 .and_then(|v| v.as_u64())
649 .ok_or_else(|| EngineError::malformed("matrix rows is missing"))?;
650 let cols = object
651 .get("cols")
652 .and_then(|v| v.as_u64())
653 .ok_or_else(|| EngineError::malformed("matrix cols is missing"))?;
654 if rows == 0 || cols == 0 {
655 return Err(EngineError::malformed("matrix dimensions must be positive"));
656 }
657 if (rows as usize).saturating_mul(cols as usize) > limits.max_matrix_elements {
658 return Err(EngineError::new(
659 ErrorCode::ResourceLimit,
660 format!(
661 "matrix {rows}x{cols} exceeds the element limit of {}",
662 limits.max_matrix_elements
663 ),
664 ));
665 }
666 let raw = object
667 .get("data")
668 .and_then(|v| v.as_array())
669 .ok_or_else(|| EngineError::malformed("matrix data must be an array"))?;
670 if raw.len() != (rows as usize) * (cols as usize) {
671 return Err(EngineError::malformed(format!(
672 "matrix data length {} does not match {}x{}",
673 raw.len(),
674 rows,
675 cols
676 )));
677 }
678 let mut data = Vec::with_capacity(raw.len());
679 for item in raw {
680 let value = Value::from_json_at(item, limits, depth + 1)?;
681 if !matches!(value, Value::Number(_)) {
682 return Err(EngineError::malformed(
683 "matrix elements must be scalar numbers",
684 ));
685 }
686 data.push(value);
687 }
688 Ok(Some(Value::Matrix {
689 rows: rows as u32,
690 cols: cols as u32,
691 data,
692 }))
693 }
694 "bound" => {
695 if object.get("unbounded").and_then(|v| v.as_bool()) == Some(true) {
696 Ok(Some(Value::Bound(Bound::Unbounded)))
697 } else if let Some(raw) = object.get("value") {
698 Ok(Some(Value::Bound(Bound::Finite(number_from(raw)?))))
699 } else {
700 Err(EngineError::malformed(
701 "bound must carry either \"unbounded\": true or a finite value",
702 ))
703 }
704 }
705 other => Err(EngineError::malformed(format!(
706 "unknown tagged value kind {other:?}"
707 ))),
708 }
709}
710
711pub fn validate_currency(currency: &str) -> Result<(), EngineError> {
714 let mut chars = currency.chars();
715 match chars.next() {
716 Some(c) if c.is_ascii_alphabetic() => {}
717 _ => {
718 return Err(EngineError::malformed(format!(
719 "invalid currency identifier {currency:?}"
720 )));
721 }
722 }
723 if currency.len() < 2 || currency.len() > 12 {
724 return Err(EngineError::malformed(format!(
725 "invalid currency identifier {currency:?}: length must be 2-12"
726 )));
727 }
728 if !currency
729 .chars()
730 .all(|c| c.is_ascii_uppercase() || c.is_ascii_digit())
731 {
732 return Err(EngineError::malformed(format!(
733 "invalid currency identifier {currency:?}: expected uppercase letters and digits"
734 )));
735 }
736 Ok(())
737}
738
739impl Serialize for Value {
740 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
741 use serde::ser::SerializeMap;
742 match self {
743 Value::Null => serializer.serialize_none(),
744 Value::Bool(value) => serializer.serialize_bool(*value),
745 Value::Text(text) => serializer.serialize_str(text),
746 Value::Number(number) => number.serialize(serializer),
747 Value::Array(items) => items.serialize(serializer),
748 Value::Record(fields) => fields.serialize(serializer),
749 Value::Quantity { value, dimension } => {
750 let mut map = serializer.serialize_map(Some(3))?;
751 map.serialize_entry("kind", "quantity")?;
752 map.serialize_entry("value", value)?;
753 map.serialize_entry("dimension", dimension)?;
754 map.end()
755 }
756 Value::Money { amount, currency } => {
757 let mut map = serializer.serialize_map(Some(3))?;
758 map.serialize_entry("kind", "money")?;
759 map.serialize_entry("amount", amount)?;
760 map.serialize_entry("currency", currency)?;
761 map.end()
762 }
763 Value::Matrix { rows, cols, data } => {
764 let mut map = serializer.serialize_map(Some(4))?;
765 map.serialize_entry("kind", "matrix")?;
766 map.serialize_entry("rows", rows)?;
767 map.serialize_entry("cols", cols)?;
768 map.serialize_entry("data", data)?;
769 map.end()
770 }
771 Value::Bound(bound) => {
772 let mut map = serializer.serialize_map(Some(2))?;
773 map.serialize_entry("kind", "bound")?;
774 match bound {
775 Bound::Unbounded => map.serialize_entry("unbounded", &true)?,
776 Bound::Finite(number) => map.serialize_entry("value", number)?,
777 }
778 map.end()
779 }
780 }
781 }
782}
783
784impl<'de> Deserialize<'de> for Value {
785 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
786 let raw = serde_json::Value::deserialize(deserializer)?;
787 Value::from_json(&raw, &Limits::conservative()).map_err(D::Error::custom)
788 }
789}
790
791impl Serialize for Number {
792 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
793 use serde::ser::SerializeMap;
794 match self {
795 Number::Integer(value) => {
796 let mut map = serializer.serialize_map(Some(2))?;
797 map.serialize_entry("kind", "integer")?;
798 map.serialize_entry("value", &value.to_string())?;
799 map.end()
800 }
801 Number::Decimal(value) => {
802 let mut map = serializer.serialize_map(Some(2))?;
803 map.serialize_entry("kind", "decimal")?;
804 map.serialize_entry("value", &value.to_plain_string())?;
805 map.end()
806 }
807 Number::Rational(value) => {
808 let mut map = serializer.serialize_map(Some(3))?;
809 map.serialize_entry("kind", "rational")?;
810 map.serialize_entry("numerator", &value.numer().to_string())?;
811 map.serialize_entry("denominator", &value.denom().to_string())?;
812 map.end()
813 }
814 Number::Float64(value) => {
815 let mut map = serializer.serialize_map(Some(2))?;
816 map.serialize_entry("kind", "float64")?;
817 map.serialize_entry("value", &value.to_string())?;
818 map.end()
819 }
820 }
821 }
822}
823
824impl<'de> Deserialize<'de> for Number {
825 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
826 let value = Value::deserialize(deserializer)?;
827 match value {
828 Value::Number(number) => Ok(number),
829 other => Err(D::Error::custom(format!(
830 "expected a number, found {}",
831 other.kind_name()
832 ))),
833 }
834 }
835}
836
837#[cfg(test)]
838mod tests {
839 use super::*;
840
841 #[test]
842 fn scalar_round_trip_preserves_exact_payloads() {
843 let raw = serde_json::json!({"kind": "integer", "value": "9007199254740993"});
844 let value: Value = serde_json::from_value(raw.clone()).unwrap();
845 let encoded = serde_json::to_value(&value).unwrap();
846 assert_eq!(encoded, raw);
847 }
848
849 #[test]
850 fn decimal_scale_survives_round_trip() {
851 let raw = serde_json::json!({"kind": "decimal", "value": "0.10"});
852 let value: Value = serde_json::from_value(raw.clone()).unwrap();
853 let encoded = serde_json::to_value(&value).unwrap();
854 assert_eq!(encoded, raw);
855 }
856
857 #[test]
858 fn plain_json_integers_and_decimals_are_exact() {
859 let value: Value = serde_json::from_value(serde_json::json!(9007199254740993u64)).unwrap();
860 assert_eq!(
861 serde_json::to_value(&value).unwrap(),
862 serde_json::json!({"kind": "integer", "value": "9007199254740993"})
863 );
864 let value: Value = serde_json::from_value(serde_json::json!(0.10)).unwrap();
865 assert_eq!(
866 serde_json::to_value(&value).unwrap(),
867 serde_json::json!({"kind": "decimal", "value": "0.1"})
868 );
869 }
870
871 #[test]
872 fn quantity_round_trip() {
873 let raw = serde_json::json!({
874 "kind": "quantity",
875 "value": {"kind": "decimal", "value": "9.81"},
876 "dimension": {"length": 1, "time": -2}
877 });
878 let value: Value = serde_json::from_value(raw.clone()).unwrap();
879 assert_eq!(serde_json::to_value(&value).unwrap(), raw);
880 }
881
882 #[test]
883 fn money_requires_valid_currency() {
884 let raw = serde_json::json!({
885 "kind": "money",
886 "amount": {"kind": "decimal", "value": "10.00"},
887 "currency": "usd"
888 });
889 assert!(serde_json::from_value::<Value>(raw).is_err());
890 }
891
892 #[test]
893 fn record_key_kind_is_rejected() {
894 let raw = serde_json::json!({"kind": "not-a-real-tag", "value": 1});
895 assert!(serde_json::from_value::<Value>(raw).is_err());
896 }
897
898 #[test]
899 fn matrix_shape_is_checked() {
900 let raw = serde_json::json!({
901 "kind": "matrix", "rows": 2, "cols": 2,
902 "data": [1, 2, 3]
903 });
904 assert!(serde_json::from_value::<Value>(raw).is_err());
905 }
906}