1use crate::encode::ProbeError;
45use crate::sink::{Aggregate, Sink};
46use crate::source::Source;
47use serde_json::{Map, Number, Value};
48use std::string::String;
49use std::vec::Vec;
50use zerodds_ast::PrimitiveKind;
51use zerodds_float128::Float128;
52
53pub const EXACT_JSON_INTEGER: i128 = 1 << 53;
60
61impl Source for Value {
62 fn kind(&self) -> &'static str {
63 match self {
64 Self::Null => "null",
65 Self::Bool(_) => "a boolean",
66 Self::Number(_) => "a number",
67 Self::String(_) => "a string",
68 Self::Array(_) => "an array",
69 Self::Object(_) => "an object",
70 }
71 }
72
73 fn is_null(&self) -> bool {
78 Self::is_null(self)
79 }
80
81 fn is_object(&self) -> bool {
82 Self::is_object(self)
83 }
84
85 fn as_bool(&self) -> Option<bool> {
86 Self::as_bool(self)
87 }
88
89 fn as_text(&self) -> Option<&str> {
90 self.as_str()
91 }
92
93 fn as_float(&self) -> Option<f64> {
94 self.as_f64()
95 }
96
97 fn as_items(&self) -> Option<&[Self]> {
98 self.as_array().map(Vec::as_slice)
99 }
100
101 fn field(&self, name: &str) -> Option<&Self> {
102 self.as_object().and_then(|map| map.get(name))
103 }
104
105 fn entries(&self) -> Vec<(&str, &Self)> {
106 self.as_object().map_or_else(Vec::new, |map| {
107 map.iter()
108 .map(|(name, value)| (name.as_str(), value))
109 .collect()
110 })
111 }
112
113 fn as_integer(&self, path: &str) -> Result<i128, ProbeError> {
114 if let Some(text) = self.as_str() {
115 return text.parse::<i128>().map_err(|_| ProbeError::Mismatch {
116 path: path.to_string(),
117 expected: "an integer in a string".to_string(),
118 found: format!("`{text}`"),
119 });
120 }
121 let number = self
122 .as_i64()
123 .map(i128::from)
124 .or_else(|| self.as_u64().map(i128::from))
125 .ok_or_else(|| ProbeError::Mismatch {
126 path: path.to_string(),
127 expected: "an integer".to_string(),
128 found: Source::kind(self).to_string(),
129 })?;
130 if number.abs() > EXACT_JSON_INTEGER {
131 return Err(ProbeError::Mismatch {
132 path: path.to_string(),
133 expected: "an integer past 2^53 written as a string, which a JSON number \
134 cannot carry exactly"
135 .to_string(),
136 found: format!("the number {number}"),
137 });
138 }
139 Ok(number)
140 }
141
142 fn as_float128(&self, path: &str) -> Result<Float128, ProbeError> {
143 match self {
144 Self::String(text) => match float128_from_hex_bits(text) {
145 Some(Ok(value)) => Ok(value),
146 Some(Err(expected)) => Err(ProbeError::Mismatch {
147 path: path.to_string(),
148 expected: expected.to_string(),
149 found: format!("`{text}`"),
150 }),
151 None => text
152 .parse::<Float128>()
153 .map_err(|error| ProbeError::Mismatch {
154 path: path.to_string(),
155 expected: "a decimal a long double can hold, or 0x and 32 hex digits"
156 .to_string(),
157 found: format!("`{text}` — {error}"),
158 }),
159 },
160 Self::Number(_) => {
165 self.as_f64()
166 .map(Float128::from_f64)
167 .ok_or_else(|| ProbeError::Mismatch {
168 path: path.to_string(),
169 expected: "a number".to_string(),
170 found: Source::kind(self).to_string(),
171 })
172 }
173 other => Err(ProbeError::Mismatch {
174 path: path.to_string(),
175 expected: "a long double, as a decimal string or a number".to_string(),
176 found: Source::kind(other).to_string(),
177 }),
178 }
179 }
180
181 fn render(&self) -> String {
182 let mut out = String::new();
183 write_text(self, &mut out);
184 out
185 }
186}
187
188fn write_text(value: &Value, out: &mut String) {
204 use std::fmt::Write as _;
205 match value {
206 Value::Object(map) => {
207 let mut keys: Vec<&String> = map.keys().collect();
208 keys.sort();
209 out.push('{');
210 for (index, key) in keys.into_iter().enumerate() {
211 if index > 0 {
212 out.push(',');
213 }
214 write_text(&Value::String(key.clone()), out);
215 out.push(':');
216 match map.get(key) {
217 Some(entry) => write_text(entry, out),
218 None => out.push_str("\"<missing>\""),
223 }
224 }
225 out.push('}');
226 }
227 Value::Array(items) => {
228 out.push('[');
229 for (index, item) in items.iter().enumerate() {
230 if index > 0 {
231 out.push(',');
232 }
233 write_text(item, out);
234 }
235 out.push(']');
236 }
237 Value::String(text) => {
238 out.push('"');
239 for ch in text.chars() {
240 match ch {
241 '"' => out.push_str("\\\""),
242 '\\' => out.push_str("\\\\"),
243 '\n' => out.push_str("\\n"),
244 '\r' => out.push_str("\\r"),
245 '\t' => out.push_str("\\t"),
246 control if (control as u32) < 0x20 => {
247 let _ = write!(out, "\\u{:04x}", control as u32);
248 }
249 other => out.push(other),
250 }
251 }
252 out.push('"');
253 }
254 Value::Number(number) => out.push_str(&number.to_string()),
255 Value::Bool(true) => out.push_str("true"),
256 Value::Bool(false) => out.push_str("false"),
257 Value::Null => out.push_str("null"),
258 }
259}
260
261impl Sink for Value {
262 fn boolean(value: bool) -> Self {
263 Self::Bool(value)
264 }
265
266 fn integer(kind: PrimitiveKind, value: i128, path: &str) -> Result<Self, ProbeError> {
267 if matches!(kind, PrimitiveKind::Int64 | PrimitiveKind::UInt64) {
273 return Ok(Self::String(format!("{value}")));
274 }
275 match i64::try_from(value) {
276 Ok(fits) => Ok(Self::Number(fits.into())),
277 Err(_) => Err(ProbeError::Mismatch {
281 path: path.to_string(),
282 expected: "an integer a JSON number can hold".to_string(),
283 found: format!("{value}"),
284 }),
285 }
286 }
287
288 fn float(kind: PrimitiveKind, value: f64, path: &str) -> Result<Self, ProbeError> {
289 let _ = kind;
290 Self::number(value, path)
291 }
292
293 fn float128(value: Float128) -> Self {
294 Self::String(value.to_exact_decimal())
304 }
305
306 fn character(kind: PrimitiveKind, value: char) -> Self {
307 let _ = kind;
312 Self::String(value.to_string())
313 }
314
315 fn text(value: String) -> Self {
316 Self::String(value)
317 }
318
319 fn fixed(decimal: String) -> Self {
320 Self::String(decimal)
321 }
322
323 fn enumerator(name: String) -> Self {
324 Self::String(name)
325 }
326
327 fn bit_names(names: Vec<String>) -> Self {
328 Self::Array(names.into_iter().map(Self::String).collect())
329 }
330
331 fn bitfields(fields: Vec<(String, i128)>, path: &str) -> Result<Self, ProbeError> {
332 let mut out = Map::new();
333 for (name, raw) in fields {
334 match i64::try_from(raw) {
335 Ok(number) => {
336 out.insert(name, Self::Number(number.into()));
337 }
338 Err(_) => {
339 return Err(ProbeError::Mismatch {
340 path: format!("{path}.{name}"),
341 expected: "a bitfield a JSON number can hold".to_string(),
342 found: format!("{raw}"),
343 });
344 }
345 }
346 }
347 Ok(Self::Object(out))
348 }
349
350 fn collection(items: Vec<Self>) -> Self {
351 Self::Array(items)
352 }
353
354 fn map_entry(key: Self, value: Self) -> Self {
355 Self::Array(vec![key, value])
356 }
357
358 fn aggregate(members: Aggregate<Self>) -> Self {
359 Self::Object(members.into_entries().into_iter().collect())
360 }
361
362 fn number(value: f64, path: &str) -> Result<Self, ProbeError> {
363 match Number::from_f64(value) {
368 Some(number) => Ok(Self::Number(number)),
369 None => Err(ProbeError::Mismatch {
370 path: path.to_string(),
371 expected: "a finite number, which is all JSON can write".to_string(),
372 found: format!("{value}"),
373 }),
374 }
375 }
376}
377
378#[cfg(test)]
379mod tests {
380 #![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
381 use super::*;
382 use crate::contract::{Member, character, integer, integer_for, member_value, object};
383 use serde_json::json;
384 use zerodds_ast_core::Id;
385
386 #[test]
387 fn an_integer_past_two_to_the_fifty_third_has_to_be_a_string() {
388 assert_eq!(
389 integer(&json!("9007199254740993"), "p"),
390 Ok(9_007_199_254_740_993)
391 );
392 let refused = integer(&json!(9_007_199_254_740_993i64), "p").expect_err("a refusal");
393 assert!(matches!(refused, ProbeError::Mismatch { ref path, .. } if path == "p"));
394 assert_eq!(
396 integer(&json!(9_007_199_254_740_992i64), "p"),
397 Ok(EXACT_JSON_INTEGER)
398 );
399 }
400
401 #[test]
402 fn a_value_outside_its_primitive_is_refused_and_not_wrapped() {
403 assert_eq!(integer_for(PrimitiveKind::Octet, &json!(255), "p"), Ok(255));
404 assert!(integer_for(PrimitiveKind::Octet, &json!(300), "p").is_err());
405 assert!(integer_for(PrimitiveKind::Int8, &json!(-129), "p").is_err());
406 assert!(integer_for(PrimitiveKind::UInt32, &json!(-1), "p").is_err());
407 }
408
409 #[test]
410 fn a_char_is_one_character_and_never_the_first_of_several() {
411 assert_eq!(character(&json!("a"), "p"), Ok('a'));
412 assert!(character(&json!("ab"), "p").is_err());
413 assert!(character(&json!(""), "p").is_err());
414 }
415
416 #[test]
417 fn null_is_neither_a_value_nor_an_absence() {
418 let value = json!({ "a": null });
419 let fields = object(&value, "S").expect("it is an object");
420 let required = Member {
421 node: zerodds_ast_core::NodeId::from_parts(zerodds_ast_core::IdSpace::MODEL, 1),
422 name: "a".to_string(),
423 type_: Id::first_at(0),
424 optional: false,
425 };
426 let optional = Member {
427 optional: true,
428 ..required.clone()
429 };
430 for member in [&required, &optional] {
431 assert!(matches!(
432 member_value(fields, member, "S"),
433 Err(ProbeError::Mismatch { ref path, .. }) if path == "S.a"
434 ));
435 }
436 }
437
438 #[test]
439 fn a_sixty_four_bit_width_is_a_string_at_every_magnitude() {
440 assert_eq!(
443 <Value as Sink>::integer(PrimitiveKind::Int64, 3, "p"),
444 Ok(Value::String("3".to_string()))
445 );
446 assert_eq!(
447 <Value as Sink>::integer(PrimitiveKind::Int32, 3, "p"),
448 Ok(Value::Number(3.into()))
449 );
450 }
451}
452
453fn float128_from_hex_bits(text: &str) -> Option<Result<Float128, &'static str>> {
469 let digits = text
470 .strip_prefix("0x")
471 .or_else(|| text.strip_prefix("0X"))?;
472 if digits.len() != 32 {
473 return Some(Err(
474 "exactly 32 hexadecimal digits, one per binary128 nibble",
475 ));
476 }
477 let nibble = |b: u8| match b {
482 b'0'..=b'9' => Some(b - b'0'),
483 b'a'..=b'f' => Some(b - b'a' + 10),
484 b'A'..=b'F' => Some(b - b'A' + 10),
485 _ => None,
486 };
487 let mut octets = [0u8; 16];
488 for (slot, pair) in octets.iter_mut().zip(digits.as_bytes().chunks_exact(2)) {
489 match (nibble(pair[0]), nibble(pair[1])) {
490 (Some(high), Some(low)) => *slot = (high << 4) | low,
491 _ => return Some(Err("a hexadecimal digit")),
492 }
493 }
494 Some(Ok(Float128::from_le_bytes(octets)))
495}
496
497#[cfg(test)]
498mod float128_hex_tests {
499 #![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
500 use super::*;
501
502 #[test]
508 fn the_suites_own_float128_literals_are_read_as_bits() {
509 let half = float128_from_hex_bits("0x0000000000000000000000000000fe3f")
510 .expect("recognised as hex")
511 .expect("well formed");
512 assert_eq!(half.to_le_bytes()[15], 0x3f, "0.5 keeps its exponent octet");
513 assert_eq!(half, Float128::from_f64(0.5), "struct_float128_x1 is 0.5");
514
515 let twelve_three = float128_from_hex_bits("0x9A999999999999999999999999890240")
516 .expect("recognised as hex")
517 .expect("well formed");
518 let want: [u8; 16] = [
519 0x9A, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x89,
520 0x02, 0x40,
521 ];
522 assert_eq!(
523 twelve_three.to_le_bytes(),
524 want,
525 "struct_primitives x12 round-trips its octets"
526 );
527 }
528
529 #[test]
530 fn a_decimal_still_reaches_the_decimal_parser() {
531 assert!(
532 float128_from_hex_bits("12.3").is_none(),
533 "a decimal is not this spelling and must fall through"
534 );
535 }
536
537 #[test]
538 fn a_malformed_hex_literal_is_refused_rather_than_substituted() {
539 assert!(
540 matches!(float128_from_hex_bits("0xfe3f"), Some(Err(_))),
541 "too few digits is an error, not a zero"
542 );
543 assert!(
544 matches!(float128_from_hex_bits("0xZZ"), Some(Err(_))),
545 "a non-hex digit is an error, not a zero"
546 );
547 }
548}