1use std::cmp::Ordering;
8use std::error::Error;
9use std::fmt;
10
11use serde_json::Value;
12
13pub type JsonValue = Value;
15pub type JsonCodecResult<T> = Result<T, JsonCodecError>;
17
18const JS_MAX_SAFE_INTEGER_F64: f64 = 9_007_199_254_740_991.0;
19const JS_MAX_SAFE_INTEGER_I64: i64 = 9_007_199_254_740_991;
20const JS_MAX_SAFE_INTEGER_U64: u64 = 9_007_199_254_740_991;
21
22#[derive(Clone, Debug, Eq, PartialEq)]
23pub enum JsonCodecError {
25 Encode(String),
27 Decode(String),
29 Validation(String),
31}
32
33impl JsonCodecError {
34 pub fn encode(message: impl Into<String>) -> Self {
36 Self::Encode(message.into())
37 }
38
39 pub fn decode(message: impl Into<String>) -> Self {
41 Self::Decode(message.into())
42 }
43
44 pub fn validation(message: impl Into<String>) -> Self {
46 Self::Validation(message.into())
47 }
48}
49
50impl fmt::Display for JsonCodecError {
51 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52 match self {
53 Self::Encode(message) => write!(f, "json encode failed: {message}"),
54 Self::Decode(message) => write!(f, "json decode failed: {message}"),
55 Self::Validation(message) => f.write_str(message),
56 }
57 }
58}
59
60impl Error for JsonCodecError {}
61
62pub trait Codec<T> {
64 fn encode(&self, value: &T) -> JsonCodecResult<Vec<u8>>;
66 fn decode(&self, bytes: &[u8]) -> JsonCodecResult<T>;
68}
69
70#[derive(Clone, Copy, Debug, Default)]
71pub struct JsonCodec;
73
74impl Codec<JsonValue> for JsonCodec {
75 fn encode(&self, value: &JsonValue) -> JsonCodecResult<Vec<u8>> {
76 stable_json_string(value).map(String::into_bytes)
77 }
78
79 fn decode(&self, bytes: &[u8]) -> JsonCodecResult<JsonValue> {
80 serde_json::from_slice(bytes).map_err(|err| JsonCodecError::decode(err.to_string()))
81 }
82}
83
84#[derive(Clone, Copy, Debug, Default)]
85pub struct StrictJsonCodec;
87
88impl Codec<JsonValue> for StrictJsonCodec {
89 fn encode(&self, value: &JsonValue) -> JsonCodecResult<Vec<u8>> {
90 strict_canonical_json_bytes(value)
91 }
92
93 fn decode(&self, bytes: &[u8]) -> JsonCodecResult<JsonValue> {
94 strict_json_decode(bytes)
95 }
96}
97
98pub fn json_codec_for<T>() -> JsonCodec {
100 let _ = std::marker::PhantomData::<T>;
101 JsonCodec
102}
103
104pub fn strict_json_codec_for<T>() -> StrictJsonCodec {
106 let _ = std::marker::PhantomData::<T>;
107 StrictJsonCodec
108}
109
110pub fn stable_json_string(value: &JsonValue) -> JsonCodecResult<String> {
112 validate_strict_json_value(value, "$")?;
113 canonical_json_string(value)
114}
115
116pub fn strict_canonical_json_bytes(value: &JsonValue) -> JsonCodecResult<Vec<u8>> {
118 stable_json_string(value).map(String::into_bytes)
119}
120
121pub fn strict_json_decode(bytes: &[u8]) -> JsonCodecResult<JsonValue> {
124 let text = std::str::from_utf8(bytes).map_err(|err| JsonCodecError::decode(err.to_string()))?;
125 assert_no_duplicate_json_object_keys(text)?;
126 let decoded: JsonValue =
127 serde_json::from_str(text).map_err(|err| JsonCodecError::decode(err.to_string()))?;
128 let canonical = strict_canonical_json_bytes(&decoded)?;
129 if bytes != canonical.as_slice() {
130 return Err(JsonCodecError::validation(
131 "strictJsonCodec: bytes are not canonical stable JSON",
132 ));
133 }
134 Ok(decoded)
135}
136
137pub fn validate_strict_json_value(value: &JsonValue, path: &str) -> JsonCodecResult<()> {
139 validate_strict_json_value_inner(value, path, 0)
140}
141
142fn validate_strict_json_value_inner(
143 value: &JsonValue,
144 path: &str,
145 depth: u32,
146) -> JsonCodecResult<()> {
147 if depth > 128 {
148 return Err(JsonCodecError::validation(format!(
149 "strictJsonCodec: JSON value at {path} exceeds maximum depth 128"
150 )));
151 }
152 match value {
153 Value::Object(map) => {
154 for (key, value) in map {
155 validate_strict_json_value_inner(value, &format!("{path}.{key}"), depth + 1)?;
156 }
157 }
158 Value::Array(values) => {
159 for (index, value) in values.iter().enumerate() {
160 validate_strict_json_value_inner(value, &format!("{path}[{index}]"), depth + 1)?;
161 }
162 }
163 Value::Number(number) => {
164 let text = number.to_string();
165 if text == "-0.0" || text == "-0" {
166 return Err(JsonCodecError::validation(format!(
167 "strictJsonCodec: JSON number at {path} is not strict canonical JSON compatible"
168 )));
169 }
170 if let Some(value) = number.as_i64() {
171 if !(-JS_MAX_SAFE_INTEGER_I64..=JS_MAX_SAFE_INTEGER_I64).contains(&value) {
172 return Err(JsonCodecError::validation(format!(
173 "strictJsonCodec: JSON integer at {path} is outside the safe integer range"
174 )));
175 }
176 } else if let Some(value) = number.as_u64() {
177 if value > JS_MAX_SAFE_INTEGER_U64 {
178 return Err(JsonCodecError::validation(format!(
179 "strictJsonCodec: JSON integer at {path} is outside the safe integer range"
180 )));
181 }
182 }
183 if let Some(float) = number.as_f64() {
184 let abs = float.abs();
185 if abs > 0.0 && abs < f64::MIN_POSITIVE {
186 return Err(JsonCodecError::validation(format!(
187 "strictJsonCodec: JSON number at {path} is subnormal and not strict canonical JSON compatible"
188 )));
189 }
190 if float.fract() == 0.0 && abs > JS_MAX_SAFE_INTEGER_F64 {
191 return Err(JsonCodecError::validation(format!(
192 "strictJsonCodec: JSON integer at {path} is outside the safe integer range"
193 )));
194 }
195 }
196 }
197 Value::Null | Value::Bool(_) | Value::String(_) => {}
198 }
199 Ok(())
200}
201
202fn canonical_json_string(value: &JsonValue) -> JsonCodecResult<String> {
203 match value {
204 Value::Null => Ok("null".to_owned()),
205 Value::Bool(true) => Ok("true".to_owned()),
206 Value::Bool(false) => Ok("false".to_owned()),
207 Value::Number(number) => Ok(canonical_number_string(number)),
208 Value::String(value) => {
209 serde_json::to_string(value).map_err(|err| JsonCodecError::encode(err.to_string()))
210 }
211 Value::Array(values) => {
212 let mut out = String::from("[");
213 for (index, value) in values.iter().enumerate() {
214 if index > 0 {
215 out.push(',');
216 }
217 out.push_str(&canonical_json_string(value)?);
218 }
219 out.push(']');
220 Ok(out)
221 }
222 Value::Object(map) => {
223 let mut keys = map.keys().collect::<Vec<_>>();
224 keys.sort_by(|a, b| cmp_js_utf16(a, b));
225 let mut out = String::from("{");
226 for (index, key) in keys.iter().enumerate() {
227 if index > 0 {
228 out.push(',');
229 }
230 out.push_str(
231 &serde_json::to_string(key)
232 .map_err(|err| JsonCodecError::encode(err.to_string()))?,
233 );
234 out.push(':');
235 out.push_str(&canonical_json_string(
236 map.get(*key).expect("key came from map.keys()"),
237 )?);
238 }
239 out.push('}');
240 Ok(out)
241 }
242 }
243}
244
245fn canonical_number_string(number: &serde_json::Number) -> String {
246 if let Some(value) = number.as_i64() {
247 return value.to_string();
248 }
249 if let Some(value) = number.as_u64() {
250 return value.to_string();
251 }
252 if let Some(value) = number.as_f64() {
253 if value == 0.0 {
254 return "0".to_owned();
255 }
256 if value.is_finite() && value.fract() == 0.0 && value.abs() <= JS_MAX_SAFE_INTEGER_F64 {
257 return format!("{value:.0}");
258 }
259 }
260 number.to_string()
261}
262
263fn cmp_js_utf16(a: &str, b: &str) -> Ordering {
264 a.encode_utf16().cmp(b.encode_utf16())
265}
266
267fn assert_no_duplicate_json_object_keys(text: &str) -> JsonCodecResult<()> {
268 struct Scanner<'a> {
269 text: &'a str,
270 index: usize,
271 }
272
273 impl Scanner<'_> {
274 fn fail<T>(&self, message: impl Into<String>) -> JsonCodecResult<T> {
275 Err(JsonCodecError::validation(format!(
276 "strictJsonCodec: {}",
277 message.into()
278 )))
279 }
280
281 fn peek(&self) -> Option<u8> {
282 self.text.as_bytes().get(self.index).copied()
283 }
284
285 fn skip_whitespace(&mut self) {
286 while matches!(self.peek(), Some(b' ' | b'\n' | b'\r' | b'\t')) {
287 self.index += 1;
288 }
289 }
290
291 fn read_json_string(&mut self) -> JsonCodecResult<String> {
292 let start = self.index;
293 self.index += 1;
294 while self.index < self.text.len() {
295 match self.peek() {
296 Some(b'"') => {
297 self.index += 1;
298 return serde_json::from_str(&self.text[start..self.index]).map_err(
299 |err| {
300 JsonCodecError::validation(format!(
301 "strictJsonCodec: malformed JSON string: {err}"
302 ))
303 },
304 );
305 }
306 Some(b'\\') => {
307 self.index += 2;
308 }
309 Some(_) => {
310 self.index += 1;
311 }
312 None => break,
313 }
314 }
315 self.fail("unterminated JSON string")
316 }
317
318 fn consume_literal(&mut self, literal: &str) -> JsonCodecResult<()> {
319 if self.text[self.index..].starts_with(literal) {
320 self.index += literal.len();
321 Ok(())
322 } else {
323 self.fail(format!("malformed JSON near byte {}", self.index))
324 }
325 }
326
327 fn consume_number(&mut self) -> JsonCodecResult<()> {
328 let bytes = self.text.as_bytes();
329 let start = self.index;
330 if self.peek() == Some(b'-') {
331 self.index += 1;
332 }
333 match self.peek() {
334 Some(b'0') => self.index += 1,
335 Some(b'1'..=b'9') => {
336 self.index += 1;
337 while matches!(self.peek(), Some(b'0'..=b'9')) {
338 self.index += 1;
339 }
340 }
341 _ => return self.fail(format!("malformed JSON number near byte {start}")),
342 }
343 if self.peek() == Some(b'.') {
344 self.index += 1;
345 if !matches!(self.peek(), Some(b'0'..=b'9')) {
346 return self.fail(format!("malformed JSON number near byte {start}"));
347 }
348 while matches!(self.peek(), Some(b'0'..=b'9')) {
349 self.index += 1;
350 }
351 }
352 if matches!(self.peek(), Some(b'e' | b'E')) {
353 self.index += 1;
354 if matches!(self.peek(), Some(b'+' | b'-')) {
355 self.index += 1;
356 }
357 if !matches!(self.peek(), Some(b'0'..=b'9')) {
358 return self.fail(format!("malformed JSON number near byte {start}"));
359 }
360 while self.index < bytes.len() && matches!(self.peek(), Some(b'0'..=b'9')) {
361 self.index += 1;
362 }
363 }
364 Ok(())
365 }
366
367 fn parse_value(&mut self, path: &str) -> JsonCodecResult<()> {
368 self.skip_whitespace();
369 match self.peek() {
370 Some(b'{') => self.parse_object(path),
371 Some(b'[') => self.parse_array(path),
372 Some(b'"') => self.read_json_string().map(|_| ()),
373 Some(b't') => self.consume_literal("true"),
374 Some(b'f') => self.consume_literal("false"),
375 Some(b'n') => self.consume_literal("null"),
376 Some(b'-' | b'0'..=b'9') => self.consume_number(),
377 _ => self.fail(format!("malformed JSON near byte {}", self.index)),
378 }
379 }
380
381 fn parse_object(&mut self, path: &str) -> JsonCodecResult<()> {
382 let mut keys = Vec::<String>::new();
383 self.index += 1;
384 self.skip_whitespace();
385 if self.peek() == Some(b'}') {
386 self.index += 1;
387 return Ok(());
388 }
389 while self.index < self.text.len() {
390 self.skip_whitespace();
391 if self.peek() != Some(b'"') {
392 return self.fail(format!("expected object key near byte {}", self.index));
393 }
394 let key = self.read_json_string()?;
395 if keys.iter().any(|seen| seen == &key) {
396 return Err(JsonCodecError::validation(format!(
397 "strictJsonCodec: duplicate object key {:?} at {path}",
398 key
399 )));
400 }
401 keys.push(key.clone());
402 self.skip_whitespace();
403 if self.peek() != Some(b':') {
404 return self.fail(format!(
405 "expected ':' after object key near byte {}",
406 self.index
407 ));
408 }
409 self.index += 1;
410 self.parse_value(&format!("{path}.{key}"))?;
411 self.skip_whitespace();
412 match self.peek() {
413 Some(b',') => self.index += 1,
414 Some(b'}') => {
415 self.index += 1;
416 return Ok(());
417 }
418 _ => {
419 return self.fail(format!("expected ',' or '}}' near byte {}", self.index))
420 }
421 }
422 }
423 self.fail("unterminated JSON object")
424 }
425
426 fn parse_array(&mut self, path: &str) -> JsonCodecResult<()> {
427 self.index += 1;
428 self.skip_whitespace();
429 if self.peek() == Some(b']') {
430 self.index += 1;
431 return Ok(());
432 }
433 let mut item = 0;
434 while self.index < self.text.len() {
435 self.parse_value(&format!("{path}[{item}]"))?;
436 item += 1;
437 self.skip_whitespace();
438 match self.peek() {
439 Some(b',') => self.index += 1,
440 Some(b']') => {
441 self.index += 1;
442 return Ok(());
443 }
444 _ => return self.fail(format!("expected ',' or ']' near byte {}", self.index)),
445 }
446 }
447 self.fail("unterminated JSON array")
448 }
449 }
450
451 let mut scanner = Scanner { text, index: 0 };
452 scanner.parse_value("$")?;
453 scanner.skip_whitespace();
454 if scanner.index != text.len() {
455 return Err(JsonCodecError::validation(format!(
456 "strictJsonCodec: trailing JSON token near byte {}",
457 scanner.index
458 )));
459 }
460 Ok(())
461}
462
463pub type DecimalIntegerString = String;
465pub type NonNegativeDecimalIntegerString = String;
467
468pub fn is_decimal_integer_string(value: &str) -> bool {
470 if value == "0" {
471 return true;
472 }
473 let rest = value.strip_prefix('-').unwrap_or(value);
474 !rest.is_empty()
475 && !rest.starts_with('0')
476 && rest.bytes().all(|byte| byte.is_ascii_digit())
477 && value != "-0"
478}
479
480pub fn is_non_negative_decimal_integer_string(value: &str) -> bool {
482 if value == "0" {
483 return true;
484 }
485 !value.is_empty()
486 && !value.starts_with('-')
487 && !value.starts_with('0')
488 && value.bytes().all(|byte| byte.is_ascii_digit())
489}
490
491pub fn assert_decimal_integer_string(
493 value: impl Into<String>,
494 label: &str,
495) -> JsonCodecResult<DecimalIntegerString> {
496 let value = value.into();
497 if is_decimal_integer_string(&value) {
498 Ok(value)
499 } else {
500 Err(JsonCodecError::validation(format!(
501 "{label} must be a canonical decimal integer string"
502 )))
503 }
504}
505
506pub fn assert_non_negative_decimal_integer_string(
508 value: impl Into<String>,
509 label: &str,
510) -> JsonCodecResult<NonNegativeDecimalIntegerString> {
511 let value = value.into();
512 if is_non_negative_decimal_integer_string(&value) {
513 Ok(value)
514 } else {
515 Err(JsonCodecError::validation(format!(
516 "{label} must be a canonical non-negative decimal integer string"
517 )))
518 }
519}
520
521pub fn i128_to_decimal_string(value: i128) -> DecimalIntegerString {
523 value.to_string()
524}
525
526pub fn u128_to_non_negative_decimal_string(value: u128) -> NonNegativeDecimalIntegerString {
528 value.to_string()
529}
530
531pub fn decimal_string_to_i128(value: &str) -> JsonCodecResult<i128> {
533 assert_decimal_integer_string(value, "decimal integer")?
534 .parse::<i128>()
535 .map_err(|err| {
536 JsonCodecError::validation(format!("decimal integer is outside i128 range: {err}"))
537 })
538}
539
540pub fn non_negative_decimal_string_to_u128(value: &str) -> JsonCodecResult<u128> {
542 assert_non_negative_decimal_integer_string(value, "decimal integer")?
543 .parse::<u128>()
544 .map_err(|err| {
545 JsonCodecError::validation(format!("decimal integer is outside u128 range: {err}"))
546 })
547}