use num_bigint::BigInt;
use num_rational::BigRational;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Value {
Int(BigInt),
Rational(BigRational),
Unit { number: Box<Value>, unit: UnitId },
}
pub use crate::unit::UnitId;
impl Serialize for Value {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
use serde::ser::SerializeMap;
match self {
Value::Int(n) => {
let mut map = serializer.serialize_map(Some(2))?;
map.serialize_entry("kind", "int")?;
map.serialize_entry("value", &n.to_string())?;
map.end()
}
Value::Rational(r) => {
let mut map = serializer.serialize_map(Some(3))?;
map.serialize_entry("kind", "rational")?;
map.serialize_entry("numerator", &r.numer().to_string())?;
map.serialize_entry("denominator", &r.denom().to_string())?;
map.end()
}
Value::Unit { number, unit } => {
let (number_kind, numerator, denominator) = match number.as_ref() {
Value::Int(n) => ("int", n.to_string(), "1".to_string()),
Value::Rational(r) => {
("rational", r.numer().to_string(), r.denom().to_string())
}
Value::Unit { .. } => {
return Err(serde::ser::Error::custom("unit may not wrap another unit"));
}
};
let mut map = serializer.serialize_map(Some(5))?;
map.serialize_entry("kind", "unit")?;
map.serialize_entry("number_kind", number_kind)?;
map.serialize_entry("numerator", &numerator)?;
map.serialize_entry("denominator", &denominator)?;
map.serialize_entry("unit", &unit.to_string())?;
map.end()
}
}
}
}
impl<'de> Deserialize<'de> for Value {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct Raw {
kind: String,
value: Option<String>,
numerator: Option<String>,
denominator: Option<String>,
number_kind: Option<String>,
unit: Option<String>,
}
let raw = Raw::deserialize(deserializer)?;
match raw.kind.as_str() {
"int" => {
let value = raw
.value
.ok_or_else(|| serde::de::Error::missing_field("value"))?;
let n: BigInt = value
.parse()
.map_err(|_| serde::de::Error::custom("invalid integer"))?;
Ok(Value::Int(n))
}
"rational" => {
let num_str = raw
.numerator
.ok_or_else(|| serde::de::Error::missing_field("numerator"))?;
let den_str = raw
.denominator
.ok_or_else(|| serde::de::Error::missing_field("denominator"))?;
let n: BigInt = num_str
.parse()
.map_err(|_| serde::de::Error::custom("invalid numerator"))?;
let d: BigInt = den_str
.parse()
.map_err(|_| serde::de::Error::custom("invalid denominator"))?;
if d == BigInt::from(0) {
return Err(serde::de::Error::custom("zero denominator"));
}
Ok(Value::Rational(BigRational::new(n, d)))
}
"unit" => {
let number_kind = raw
.number_kind
.ok_or_else(|| serde::de::Error::missing_field("number_kind"))?;
let num_str = raw
.numerator
.ok_or_else(|| serde::de::Error::missing_field("numerator"))?;
let den_str = raw
.denominator
.ok_or_else(|| serde::de::Error::missing_field("denominator"))?;
let unit_str = raw
.unit
.ok_or_else(|| serde::de::Error::missing_field("unit"))?;
let number = match number_kind.as_str() {
"int" => {
let n: BigInt = num_str
.parse()
.map_err(|_| serde::de::Error::custom("invalid integer"))?;
Value::Int(n)
}
"rational" => {
let n: BigInt = num_str
.parse()
.map_err(|_| serde::de::Error::custom("invalid numerator"))?;
let d: BigInt = den_str
.parse()
.map_err(|_| serde::de::Error::custom("invalid denominator"))?;
if d == BigInt::from(0) {
return Err(serde::de::Error::custom("zero denominator"));
}
Value::Rational(BigRational::new(n, d))
}
_ => return Err(serde::de::Error::custom("unknown number_kind")),
};
let unit = UnitId::from_symbol(&unit_str)
.unwrap_or_else(|| UnitId::Unknown(unit_str.clone()));
Ok(Value::Unit {
number: Box::new(number),
unit,
})
}
other => Err(serde::de::Error::custom(format!("unknown kind: {}", other))),
}
}
}
impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Value::Int(n) => write!(f, "{}", n),
Value::Rational(r) => {
if r.denom() == &BigInt::from(1) {
write!(f, "{}", r.numer())
} else {
write!(f, "{}/{}", r.numer(), r.denom())
}
}
Value::Unit { number, unit } => write!(f, "{} {}", number, unit),
}
}
}