use std::hash::{Hash, Hasher};
use serde::de::Error as _;
use serde::ser::{Error as _, SerializeMap};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[derive(Debug, Clone, PartialEq)]
pub enum Value {
Number(f64),
Text(String),
Boolean(bool),
Error(String),
Empty,
Array(Vec<Vec<Value>>),
Date(f64),
}
fn normalized_bits(x: f64) -> u64 {
if x == 0.0 {
0.0_f64.to_bits()
} else {
x.to_bits()
}
}
impl Hash for Value {
fn hash<H: Hasher>(&self, state: &mut H) {
std::mem::discriminant(self).hash(state);
match self {
Value::Number(n) | Value::Date(n) => normalized_bits(*n).hash(state),
Value::Text(s) => s.hash(state),
Value::Boolean(b) => b.hash(state),
Value::Error(code) => code.hash(state),
Value::Empty => {}
Value::Array(rows) => {
rows.len().hash(state);
for row in rows {
row.len().hash(state);
for v in row {
v.hash(state);
}
}
}
}
}
}
fn serialize_tagged_number<S: Serializer>(
kind: &'static str,
n: f64,
serializer: S,
) -> Result<S::Ok, S::Error> {
if !n.is_finite() {
return Err(S::Error::custom(format!(
"non-finite {kind} value cannot be serialized (schema spec §8)"
)));
}
let n = if n == 0.0 { 0.0 } else { n };
let mut map = serializer.serialize_map(Some(2))?;
map.serialize_entry("type", kind)?;
map.serialize_entry("value", &n)?;
map.end()
}
impl Serialize for Value {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match self {
Value::Number(n) => serialize_tagged_number("number", *n, serializer),
Value::Date(n) => serialize_tagged_number("date", *n, serializer),
Value::Text(s) => {
let mut map = serializer.serialize_map(Some(2))?;
map.serialize_entry("type", "text")?;
map.serialize_entry("value", s)?;
map.end()
}
Value::Boolean(b) => {
let mut map = serializer.serialize_map(Some(2))?;
map.serialize_entry("type", "boolean")?;
map.serialize_entry("value", b)?;
map.end()
}
Value::Error(code) => {
let mut map = serializer.serialize_map(Some(2))?;
map.serialize_entry("error", code)?;
map.serialize_entry("type", "error")?;
map.end()
}
Value::Empty => {
let mut map = serializer.serialize_map(Some(2))?;
map.serialize_entry("type", "empty")?;
map.serialize_entry("value", &())?;
map.end()
}
Value::Array(rows) => {
if rows.is_empty() || rows[0].is_empty() {
return Err(S::Error::custom("array value must be non-empty"));
}
if rows.len() == 1 && rows[0].len() == 1 {
return Err(S::Error::custom(
"a 1x1 array does not exist in serialized form; collapse it \
to its scalar element (schema spec §6)",
));
}
let width = rows[0].len();
for row in rows {
if row.len() != width {
return Err(S::Error::custom("array value must be rectangular"));
}
for v in row {
if matches!(v, Value::Array(_)) {
return Err(S::Error::custom(
"array elements must be scalar values (no nested arrays)",
));
}
}
}
let mut map = serializer.serialize_map(Some(2))?;
map.serialize_entry("type", "array")?;
map.serialize_entry("value", rows)?;
map.end()
}
}
}
}
impl<'de> Deserialize<'de> for Value {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let raw = serde_json::Value::deserialize(deserializer)?;
parse_value(&raw).map_err(D::Error::custom)
}
}
fn parse_value(raw: &serde_json::Value) -> Result<Value, String> {
let obj = match raw.as_object() {
Some(obj) => obj,
None => return Err("a cell value must be a JSON object".to_string()),
};
let kind = match obj.get("type").and_then(serde_json::Value::as_str) {
Some(kind) => kind,
None => return Err("a cell value requires a string \"type\" field".to_string()),
};
let payload_key = if kind == "error" { "error" } else { "value" };
let payload = match obj.get(payload_key) {
Some(payload) if obj.len() == 2 => payload,
_ => {
return Err(format!(
"a {kind} value must have exactly the fields \"type\" and \"{payload_key}\""
));
}
};
match kind {
"number" => Ok(Value::Number(parse_finite_f64(payload, kind)?)),
"date" => Ok(Value::Date(parse_finite_f64(payload, kind)?)),
"text" => match payload.as_str() {
Some(s) => Ok(Value::Text(s.to_owned())),
None => Err("a text value must be a JSON string".to_string()),
},
"boolean" => match payload.as_bool() {
Some(b) => Ok(Value::Boolean(b)),
None => Err("a boolean value must be a JSON boolean".to_string()),
},
"error" => match payload.as_str() {
Some(code) => Ok(Value::Error(code.to_owned())),
None => Err("an error value must carry a string error code".to_string()),
},
"empty" => {
if payload.is_null() {
Ok(Value::Empty)
} else {
Err("an empty value must be JSON null".to_string())
}
}
"array" => parse_array(payload),
other => Err(format!("unknown value type {other:?}")),
}
}
fn parse_finite_f64(payload: &serde_json::Value, kind: &str) -> Result<f64, String> {
let n = match payload.as_f64() {
Some(n) => n,
None => return Err(format!("a {kind} value must be a JSON number")),
};
if !n.is_finite() {
return Err(format!(
"non-finite {kind} value is forbidden (schema spec §8)"
));
}
Ok(if n == 0.0 { 0.0 } else { n })
}
fn parse_array(payload: &serde_json::Value) -> Result<Value, String> {
let raw_rows = match payload.as_array() {
Some(rows) => rows,
None => return Err("an array value must be a 2-D JSON array".to_string()),
};
if raw_rows.is_empty() {
return Err("array value must be non-empty".to_string());
}
let mut rows = Vec::with_capacity(raw_rows.len());
let mut width = None;
for raw_row in raw_rows {
let raw_row = match raw_row.as_array() {
Some(row) => row,
None => return Err("array rows must be JSON arrays".to_string()),
};
if raw_row.is_empty() {
return Err("array value must be non-empty".to_string());
}
match width {
None => width = Some(raw_row.len()),
Some(w) if w != raw_row.len() => {
return Err("array value must be rectangular".to_string());
}
Some(_) => {}
}
let mut row = Vec::with_capacity(raw_row.len());
for raw_elem in raw_row {
let elem = parse_value(raw_elem)?;
if matches!(elem, Value::Array(_)) {
return Err("array elements must be scalar values (no nested arrays)".to_string());
}
row.push(elem);
}
rows.push(row);
}
if rows.len() == 1 && rows[0].len() == 1 {
return Err(
"a 1x1 array does not exist in serialized form; it must be collapsed \
to its scalar element (schema spec §6)"
.to_string(),
);
}
Ok(Value::Array(rows))
}