1use std::hash::{Hash, Hasher};
2
3use serde::de::Error as _;
4use serde::ser::{Error as _, SerializeMap};
5use serde::{Deserialize, Deserializer, Serialize, Serializer};
6use truecalc_core::types::zoned::parse_rfc9557;
7use truecalc_core::types::{SparklineChartType, SparklineSpec, SparklineValue, ZonedInstant};
8
9#[derive(Debug, Clone)]
31pub enum Value {
32 Number(f64),
34 Text(String),
36 Boolean(bool),
38 Error(String),
41 ErrorMsg(String, String),
50 Empty,
54 Array(Vec<Vec<Value>>),
57 Date(f64),
60 Zoned(Box<ZonedInstant>),
63 Sparkline(Box<SparklineSpec>),
80}
81
82fn normalized_bits(x: f64) -> u64 {
86 if x == 0.0 {
87 0.0_f64.to_bits()
88 } else {
89 x.to_bits()
90 }
91}
92
93impl Value {
94 fn error_code(&self) -> Option<&str> {
97 match self {
98 Value::Error(code) | Value::ErrorMsg(code, _) => Some(code.as_str()),
99 _ => None,
100 }
101 }
102
103 pub fn error_message(&self) -> Option<&str> {
106 match self {
107 Value::ErrorMsg(_, msg) => Some(msg.as_str()),
108 _ => None,
109 }
110 }
111}
112
113impl PartialEq for Value {
119 fn eq(&self, other: &Self) -> bool {
120 match (self, other) {
121 (Value::Number(a), Value::Number(b)) => a == b,
122 (Value::Text(a), Value::Text(b)) => a == b,
123 (Value::Boolean(a), Value::Boolean(b)) => a == b,
124 (Value::Empty, Value::Empty) => true,
125 (Value::Array(a), Value::Array(b)) => a == b,
126 (Value::Date(a), Value::Date(b)) => a == b,
127 (Value::Zoned(a), Value::Zoned(b)) => a == b,
128 (Value::Sparkline(a), Value::Sparkline(b)) => a == b,
131 _ => match (self.error_code(), other.error_code()) {
132 (Some(a), Some(b)) => a == b,
133 _ => false,
134 },
135 }
136 }
137}
138
139impl Hash for Value {
140 fn hash<H: Hasher>(&self, state: &mut H) {
141 if let Some(code) = self.error_code() {
145 "error".hash(state);
146 code.hash(state);
147 return;
148 }
149 std::mem::discriminant(self).hash(state);
150 match self {
151 Value::Number(n) | Value::Date(n) => normalized_bits(*n).hash(state),
152 Value::Text(s) => s.hash(state),
153 Value::Boolean(b) => b.hash(state),
154 Value::Zoned(z) => z.to_rfc9557().hash(state),
157 Value::Empty => {}
158 Value::Array(rows) => {
159 rows.len().hash(state);
160 for row in rows {
161 row.len().hash(state);
162 for v in row {
163 v.hash(state);
164 }
165 }
166 }
167 Value::Sparkline(spec) => {
170 spec.chart_type.as_str().hash(state);
171 spec.data.len().hash(state);
172 for point in &spec.data {
173 hash_sparkline_value(point, state);
174 }
175 spec.options.len().hash(state);
176 for (key, value) in &spec.options {
177 key.hash(state);
178 hash_sparkline_value(value, state);
179 }
180 }
181 Value::Error(_) | Value::ErrorMsg(_, _) => unreachable!(),
183 }
184 }
185}
186
187fn sparkline_value_to_value(v: &SparklineValue) -> Value {
190 match v {
191 SparklineValue::Number(n) => Value::Number(if *n == 0.0 { 0.0 } else { *n }),
192 SparklineValue::Text(s) => Value::Text(s.clone()),
193 SparklineValue::Bool(b) => Value::Boolean(*b),
194 SparklineValue::Blank => Value::Empty,
195 }
196}
197
198fn value_to_sparkline_value(v: &Value) -> Result<SparklineValue, String> {
201 match v {
202 Value::Number(n) => Ok(SparklineValue::number(*n)),
203 Value::Text(s) => Ok(SparklineValue::Text(s.clone())),
204 Value::Boolean(b) => Ok(SparklineValue::Bool(*b)),
205 Value::Empty => Ok(SparklineValue::Blank),
206 _ => Err(
207 "a sparkline data point or option value must be a number, text, boolean or empty"
208 .to_string(),
209 ),
210 }
211}
212
213fn hash_sparkline_value<H: Hasher>(v: &SparklineValue, state: &mut H) {
214 std::mem::discriminant(v).hash(state);
215 match v {
216 SparklineValue::Number(n) => normalized_bits(*n).hash(state),
217 SparklineValue::Text(s) => s.hash(state),
218 SparklineValue::Bool(b) => b.hash(state),
219 SparklineValue::Blank => {}
220 }
221}
222
223struct SparklineSpecWire<'a>(&'a SparklineSpec);
227
228impl Serialize for SparklineSpecWire<'_> {
229 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
230 if self.0.data.len() < 2 {
237 return Err(S::Error::custom(
238 "a sparkline plots at least two points; a shorter spec does not exist \
239 in serialized form (the evaluator answers #N/A for one point)",
240 ));
241 }
242 for (key, _) in &self.0.options {
243 if *key != key.to_ascii_lowercase() {
244 return Err(S::Error::custom(format!(
245 "a sparkline option key must be lower-case, got {key:?}"
246 )));
247 }
248 if key == "charttype" {
249 return Err(S::Error::custom(
250 "charttype is carried by the sparkline's own field, not in options",
251 ));
252 }
253 }
254 let data: Vec<Value> = self.0.data.iter().map(sparkline_value_to_value).collect();
255 let options: Vec<(&str, Value)> = self
256 .0
257 .options
258 .iter()
259 .map(|(k, v)| (k.as_str(), sparkline_value_to_value(v)))
260 .collect();
261 let mut map = serializer.serialize_map(Some(3))?;
262 map.serialize_entry("charttype", self.0.chart_type.as_str())?;
263 map.serialize_entry("data", &data)?;
264 map.serialize_entry("options", &options)?;
265 map.end()
266 }
267}
268
269fn serialize_tagged_number<S: Serializer>(
270 kind: &'static str,
271 n: f64,
272 serializer: S,
273) -> Result<S::Ok, S::Error> {
274 if !n.is_finite() {
275 return Err(S::Error::custom(format!(
276 "non-finite {kind} value cannot be serialized (schema spec §8)"
277 )));
278 }
279 let n = if n == 0.0 { 0.0 } else { n };
280 let mut map = serializer.serialize_map(Some(2))?;
281 map.serialize_entry("type", kind)?;
282 map.serialize_entry("value", &n)?;
283 map.end()
284}
285
286impl Serialize for Value {
287 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
288 match self {
289 Value::Number(n) => serialize_tagged_number("number", *n, serializer),
290 Value::Date(n) => serialize_tagged_number("date", *n, serializer),
291 Value::Zoned(z) => {
292 let mut map = serializer.serialize_map(Some(2))?;
293 map.serialize_entry("type", "zoned")?;
294 map.serialize_entry("value", &z.to_rfc9557())?;
295 map.end()
296 }
297 Value::Text(s) => {
298 let mut map = serializer.serialize_map(Some(2))?;
299 map.serialize_entry("type", "text")?;
300 map.serialize_entry("value", s)?;
301 map.end()
302 }
303 Value::Sparkline(spec) => {
307 let mut map = serializer.serialize_map(Some(2))?;
308 map.serialize_entry("type", "sparkline")?;
309 map.serialize_entry("value", &SparklineSpecWire(spec))?;
310 map.end()
311 }
312 Value::Boolean(b) => {
313 let mut map = serializer.serialize_map(Some(2))?;
314 map.serialize_entry("type", "boolean")?;
315 map.serialize_entry("value", b)?;
316 map.end()
317 }
318 Value::Error(code) | Value::ErrorMsg(code, _) => {
322 let mut map = serializer.serialize_map(Some(2))?;
325 map.serialize_entry("error", code)?;
326 map.serialize_entry("type", "error")?;
327 map.end()
328 }
329 Value::Empty => {
330 let mut map = serializer.serialize_map(Some(2))?;
331 map.serialize_entry("type", "empty")?;
332 map.serialize_entry("value", &())?;
333 map.end()
334 }
335 Value::Array(rows) => {
336 if rows.is_empty() || rows[0].is_empty() {
337 return Err(S::Error::custom("array value must be non-empty"));
338 }
339 if rows.len() == 1 && rows[0].len() == 1 {
340 return Err(S::Error::custom(
341 "a 1x1 array does not exist in serialized form; collapse it \
342 to its scalar element (schema spec §6)",
343 ));
344 }
345 let width = rows[0].len();
346 for row in rows {
347 if row.len() != width {
348 return Err(S::Error::custom("array value must be rectangular"));
349 }
350 for v in row {
351 if matches!(v, Value::Array(_)) {
352 return Err(S::Error::custom(
353 "array elements must be scalar values (no nested arrays)",
354 ));
355 }
356 }
357 }
358 let mut map = serializer.serialize_map(Some(2))?;
359 map.serialize_entry("type", "array")?;
360 map.serialize_entry("value", rows)?;
361 map.end()
362 }
363 }
364 }
365}
366
367impl<'de> Deserialize<'de> for Value {
368 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
369 let raw = serde_json::Value::deserialize(deserializer)?;
372 parse_value(&raw).map_err(D::Error::custom)
373 }
374}
375
376fn parse_value(raw: &serde_json::Value) -> Result<Value, String> {
377 let obj = match raw.as_object() {
378 Some(obj) => obj,
379 None => return Err("a cell value must be a JSON object".to_string()),
380 };
381 let kind = match obj.get("type").and_then(serde_json::Value::as_str) {
382 Some(kind) => kind,
383 None => return Err("a cell value requires a string \"type\" field".to_string()),
384 };
385 let payload_key = if kind == "error" { "error" } else { "value" };
386 let payload = match obj.get(payload_key) {
387 Some(payload) if obj.len() == 2 => payload,
388 _ => {
389 return Err(format!(
390 "a {kind} value must have exactly the fields \"type\" and \"{payload_key}\""
391 ));
392 }
393 };
394 match kind {
395 "number" => Ok(Value::Number(parse_finite_f64(payload, kind)?)),
396 "date" => Ok(Value::Date(parse_finite_f64(payload, kind)?)),
397 "zoned" => match payload.as_str() {
398 Some(s) => parse_zoned(s),
399 None => Err("a zoned value must be a JSON string".to_string()),
400 },
401 "text" => match payload.as_str() {
402 Some(s) => Ok(Value::Text(s.to_owned())),
403 None => Err("a text value must be a JSON string".to_string()),
404 },
405 "boolean" => match payload.as_bool() {
406 Some(b) => Ok(Value::Boolean(b)),
407 None => Err("a boolean value must be a JSON boolean".to_string()),
408 },
409 "error" => match payload.as_str() {
410 Some(code) => Ok(Value::Error(code.to_owned())),
411 None => Err("an error value must carry a string error code".to_string()),
412 },
413 "empty" => {
414 if payload.is_null() {
415 Ok(Value::Empty)
416 } else {
417 Err("an empty value must be JSON null".to_string())
418 }
419 }
420 "array" => parse_array(payload),
421 "sparkline" => parse_sparkline(payload),
422 other => Err(format!("unknown value type {other:?}")),
423 }
424}
425
426fn parse_finite_f64(payload: &serde_json::Value, kind: &str) -> Result<f64, String> {
427 let n = match payload.as_f64() {
433 Some(n) => n,
434 None => return Err(format!("a {kind} value must be a JSON number")),
435 };
436 if !n.is_finite() {
437 return Err(format!(
438 "non-finite {kind} value is forbidden (schema spec §8)"
439 ));
440 }
441 Ok(if n == 0.0 { 0.0 } else { n })
443}
444
445fn parse_zoned(s: &str) -> Result<Value, String> {
454 let zone = s
455 .split_once('[')
456 .and_then(|(_, rest)| rest.strip_suffix(']'));
457 if s.trim() != s || zone.is_some_and(|z| z.trim() != z) {
458 return Err(format!(
459 "a zoned value must not be padded with whitespace, got {s:?}"
460 ));
461 }
462 parse_rfc9557(s)
463 .map(|zi| Value::Zoned(Box::new(zi)))
464 .ok_or_else(|| format!("a zoned value must be a valid RFC-9557 string, got {s:?}"))
465}
466
467fn parse_sparkline(payload: &serde_json::Value) -> Result<Value, String> {
470 let obj = payload
471 .as_object()
472 .ok_or_else(|| "a sparkline value must be a JSON object".to_string())?;
473 if obj.len() != 3
474 || !obj.contains_key("charttype")
475 || !obj.contains_key("data")
476 || !obj.contains_key("options")
477 {
478 return Err(
479 "a sparkline value must have exactly the fields \"charttype\", \"data\" and \"options\""
480 .to_string(),
481 );
482 }
483
484 let raw_chart_type = obj["charttype"]
485 .as_str()
486 .ok_or_else(|| "a sparkline charttype must be a JSON string".to_string())?;
487 let chart_type = SparklineChartType::parse(raw_chart_type)
494 .ok_or_else(|| format!("unknown sparkline charttype {raw_chart_type:?}"))?;
495 if chart_type.as_str() != raw_chart_type {
496 return Err(format!(
497 "a sparkline charttype must be spelled in its canonical lower-case \
498 form, got {raw_chart_type:?}"
499 ));
500 }
501
502 let raw_data = obj["data"]
503 .as_array()
504 .ok_or_else(|| "sparkline data must be a JSON array".to_string())?;
505 if raw_data.len() < 2 {
508 return Err("sparkline data must hold at least two points".to_string());
509 }
510 let mut data = Vec::with_capacity(raw_data.len());
511 for raw in raw_data {
512 data.push(value_to_sparkline_value(&parse_value(raw)?)?);
513 }
514
515 let raw_options = obj["options"]
516 .as_array()
517 .ok_or_else(|| "sparkline options must be a JSON array".to_string())?;
518 let mut options = Vec::with_capacity(raw_options.len());
519 for raw in raw_options {
520 let pair = raw
521 .as_array()
522 .filter(|p| p.len() == 2)
523 .ok_or_else(|| "a sparkline option must be a [key, value] pair".to_string())?;
524 let key = pair[0]
525 .as_str()
526 .ok_or_else(|| "a sparkline option key must be a JSON string".to_string())?;
527 if key != key.to_ascii_lowercase() {
528 return Err(format!("a sparkline option key must be lower-case, got {key:?}"));
529 }
530 if key == "charttype" {
531 return Err(
532 "charttype is carried by the sparkline's own field, not in options".to_string(),
533 );
534 }
535 options.push((key.to_owned(), value_to_sparkline_value(&parse_value(&pair[1])?)?));
536 }
537
538 Ok(Value::Sparkline(Box::new(SparklineSpec {
539 chart_type,
540 data,
541 options,
542 })))
543}
544
545fn parse_array(payload: &serde_json::Value) -> Result<Value, String> {
546 let raw_rows = match payload.as_array() {
547 Some(rows) => rows,
548 None => return Err("an array value must be a 2-D JSON array".to_string()),
549 };
550 if raw_rows.is_empty() {
551 return Err("array value must be non-empty".to_string());
552 }
553 let mut rows = Vec::with_capacity(raw_rows.len());
554 let mut width = None;
555 for raw_row in raw_rows {
556 let raw_row = match raw_row.as_array() {
557 Some(row) => row,
558 None => return Err("array rows must be JSON arrays".to_string()),
559 };
560 if raw_row.is_empty() {
561 return Err("array value must be non-empty".to_string());
562 }
563 match width {
564 None => width = Some(raw_row.len()),
565 Some(w) if w != raw_row.len() => {
566 return Err("array value must be rectangular".to_string());
567 }
568 Some(_) => {}
569 }
570 let mut row = Vec::with_capacity(raw_row.len());
571 for raw_elem in raw_row {
572 let elem = parse_value(raw_elem)?;
573 if matches!(elem, Value::Array(_)) {
574 return Err("array elements must be scalar values (no nested arrays)".to_string());
575 }
576 row.push(elem);
577 }
578 rows.push(row);
579 }
580 if rows.len() == 1 && rows[0].len() == 1 {
581 return Err(
582 "a 1x1 array does not exist in serialized form; it must be collapsed \
583 to its scalar element (schema spec §6)"
584 .to_string(),
585 );
586 }
587 Ok(Value::Array(rows))
588}