#![allow(
clippy::ref_option,
clippy::trivially_copy_pass_by_ref,
clippy::ptr_arg
)]
use serde::{Deserialize, Deserializer, Serialize, Serializer};
fn restore(v: Option<Vec<Option<f64>>>, missing: f64) -> Option<Vec<f64>> {
v.map(|xs| xs.into_iter().map(|x| x.unwrap_or(missing)).collect())
}
fn restore_all(v: Vec<Option<f64>>, missing: f64) -> Vec<f64> {
v.into_iter().map(|x| x.unwrap_or(missing)).collect()
}
pub(crate) mod upper_bounds {
use super::{Deserialize, Deserializer, Serialize, Serializer};
pub fn serialize<S: Serializer>(v: &Option<Vec<f64>>, s: S) -> Result<S::Ok, S::Error> {
v.serialize(s)
}
pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Option<Vec<f64>>, D::Error> {
let v = Option::<Vec<Option<f64>>>::deserialize(d)?;
Ok(super::restore(v, f64::INFINITY))
}
}
pub(crate) mod lower_bounds {
use super::{Deserialize, Deserializer, Serialize, Serializer};
pub fn serialize<S: Serializer>(v: &Option<Vec<f64>>, s: S) -> Result<S::Ok, S::Error> {
v.serialize(s)
}
pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Option<Vec<f64>>, D::Error> {
let v = Option::<Vec<Option<f64>>>::deserialize(d)?;
Ok(super::restore(v, f64::NEG_INFINITY))
}
}
pub(crate) mod upper_limits {
use super::{Deserialize, Deserializer, Serialize, Serializer};
pub fn serialize<S: Serializer>(v: &Vec<f64>, s: S) -> Result<S::Ok, S::Error> {
v.serialize(s)
}
pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<f64>, D::Error> {
let v = Vec::<Option<f64>>::deserialize(d)?;
Ok(super::restore_all(v, f64::INFINITY))
}
}
pub(crate) mod nan_scalar {
use super::{Deserialize, Deserializer, Serialize, Serializer};
pub fn serialize<S: Serializer>(v: &f64, s: S) -> Result<S::Ok, S::Error> {
v.serialize(s)
}
pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<f64, D::Error> {
Ok(Option::<f64>::deserialize(d)?.unwrap_or(f64::NAN))
}
}
#[cfg(feature = "schema")]
pub(crate) fn nullable_number(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
schemars::json_schema!({
"type": ["number", "null"],
"format": "double",
})
}
#[cfg(test)]
mod tests {
#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq)]
struct Probe {
#[serde(with = "super::upper_bounds")]
ub: Option<Vec<f64>>,
#[serde(with = "super::lower_bounds")]
lb: Option<Vec<f64>>,
#[serde(with = "super::nan_scalar")]
len: f64,
}
#[test]
fn null_elements_read_as_signed_infinity_and_nan() {
let p: Probe =
serde_json::from_str(r#"{"ub":[1.0,null],"lb":[null,0.0],"len":null}"#).unwrap();
assert_eq!(p.ub, Some(vec![1.0, f64::INFINITY]));
assert_eq!(p.lb, Some(vec![f64::NEG_INFINITY, 0.0]));
assert!(p.len.is_nan());
}
#[test]
fn whole_null_bound_reads_as_absent() {
let p: Probe = serde_json::from_str(r#"{"ub":null,"lb":null,"len":3.0}"#).unwrap();
assert_eq!(p.ub, None);
assert_eq!(p.lb, None);
}
#[test]
fn write_read_write_is_stable() {
let p = Probe {
ub: Some(vec![500e3, f64::INFINITY]),
lb: Some(vec![f64::NEG_INFINITY]),
len: f64::NAN,
};
let text = serde_json::to_string(&p).unwrap();
let back: Probe = serde_json::from_str(&text).unwrap();
assert_eq!(back.ub, p.ub);
assert_eq!(back.lb, p.lb);
assert!(back.len.is_nan());
assert_eq!(text, serde_json::to_string(&back).unwrap());
}
#[test]
fn an_omitted_bound_still_reads_as_absent() {
use crate::model::{DistLineCode, DistSwitch};
let code = DistLineCode::new("lc", vec![vec![0.1]], vec![vec![0.2]]);
let mut v = serde_json::to_value(&code).unwrap();
let obj = v.as_object_mut().unwrap();
obj.remove("i_max");
obj.remove("s_max");
let back: DistLineCode = serde_json::from_value(v).expect("omitted bounds must parse");
assert!(back.i_max.is_none() && back.s_max.is_none());
let sw = DistSwitch::new("sw", "a", "b", vec!["1".into()], vec!["1".into()], false);
let mut v = serde_json::to_value(&sw).unwrap();
v.as_object_mut().unwrap().remove("i_max");
let back: DistSwitch = serde_json::from_value(v).expect("omitted bound must parse");
assert!(back.i_max.is_none());
}
#[cfg(feature = "schema")]
#[test]
fn a_nullable_scalar_stays_required_in_the_schema() {
let schema = schemars::schema_for!(crate::model::DistCapacitor);
let v = serde_json::to_value(&schema).unwrap();
let required: Vec<&str> = v["required"]
.as_array()
.expect("required list")
.iter()
.map(|x| x.as_str().unwrap())
.collect();
assert!(required.contains(&"q_rated"), "required: {required:?}");
assert!(required.contains(&"v_nom"), "required: {required:?}");
assert_eq!(
v["properties"]["q_rated"]["type"],
serde_json::json!(["number", "null"])
);
}
}