Files
synapse/rust/src/canonical_json.rs
T
Erik JohnstonandGitHub 5efeac44b2 Handle arbitrary sized integers in unsigned. (#19769)
Handle arbitrary sized integers in `unsigned` (and other Rust objects
that use `serde_json::Value`)
2026-05-13 11:28:06 +01:00

946 lines
30 KiB
Rust

/*
* This file is licensed under the Affero General Public License (AGPL) version 3.
*
* Copyright (C) 2026 Element Creations Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* See the GNU Affero General Public License for more details:
* <https://www.gnu.org/licenses/agpl-3.0.html>.
*
* Originally licensed under the Apache License, Version 2.0:
* <http://www.apache.org/licenses/LICENSE-2.0>.
*
* [This file includes modifications made by Element Creations Ltd]
*/
//! Serialize a Rust data structure into canonical JSON data.
//!
//! See the [Canonical
//! JSON](https://matrix.org/docs/spec/appendices#canonical-json) docs for more
//! information.
use std::{
collections::BTreeMap,
convert::TryFrom,
io::{self, Write},
};
use serde::{ser::SerializeMap, Serializer as _};
use serde::{
ser::{Error as _, SerializeStruct},
Serialize,
};
use serde_json::{
ser::{Formatter, Serializer},
value::RawValue,
Number, Value,
};
/// The minimum integer that can be used in canonical JSON.
pub const MIN_VALID_INTEGER: i64 = -(2i64.pow(53)) + 1;
/// The maximum integer that can be used in canonical JSON.
pub const MAX_VALID_INTEGER: i64 = (2i64.pow(53)) - 1;
/// A token used by `serde_json` to identify its internal `Number` type when the
/// `arbitrary_precision` feature is enabled. This is a copy from serde_json's
/// internal `TOKEN` for `Number`, which unfortunately isn't exported by the
/// crate.
const SERDE_JSON_NUMBER_TOKEN: &str = "$serde_json::private::Number";
/// Options to control how strict JSON canonicalization is.
#[derive(Clone, Debug)]
pub struct CanonicalizationOptions {
/// Configure the serializer to strictly enforce the canonical JSON allowable number range.
/// Allows JSON for room versions v5 or less when `false`.
enforce_int_range: bool,
}
impl CanonicalizationOptions {
/// Creates an instance of [CanonicalizationOptions] with permissive JSON enforcement settings.
pub fn relaxed() -> Self {
Self {
enforce_int_range: false,
}
}
/// Creates an instance of [CanonicalizationOptions] with strict JSON enforcement settings.
pub fn strict() -> Self {
Self {
enforce_int_range: true,
}
}
}
/// Serialize the given data structure as a canonical JSON byte vector.
///
/// See the [Canonical
/// JSON](https://matrix.org/docs/spec/appendices#canonical-json) docs for more
/// information.
///
/// Note: serializing [`RawValue`] is not supported, as it may contain JSON that
/// is not canonical.
///
/// # Errors
///
/// Serialization can fail if `T`'s implementation of `Serialize` decides to
/// fail, if `T` contains a map with non-string keys, or if `T` contains numbers
/// that are not integers in the range `[-2**53 + 1, 2**53 - 1]`.
pub fn to_vec_canonical<T>(
value: &T,
options: CanonicalizationOptions,
) -> Result<Vec<u8>, serde_json::Error>
where
T: Serialize + ?Sized,
{
let mut vec = Vec::new();
let mut ser = CanonicalSerializer::new(&mut vec, options);
value.serialize(&mut ser)?;
Ok(vec)
}
/// Serialize the given data structure as a canonical JSON string.
///
/// See the [Canonical
/// JSON](https://matrix.org/docs/spec/appendices#canonical-json) docs for more
/// information.
///
/// Note: serializing [`RawValue`] is not supported, as it may contain JSON that
/// is not canonical.
///
/// # Errors
///
/// Serialization can fail if `T`'s implementation of `Serialize` decides to
/// fail, if `T` contains a map with non-string keys, or if `T` contains numbers
/// that are not integers in the range `[-2**53 + 1, 2**53 - 1]`.
pub fn to_string_canonical<T>(
value: &T,
options: CanonicalizationOptions,
) -> Result<String, serde_json::Error>
where
T: Serialize + ?Sized,
{
let vec = to_vec_canonical(value, options)?;
// We'll always get valid UTF-8 out
let json_string = String::from_utf8(vec).expect("valid utf8");
Ok(json_string)
}
/// A helper function that asserts that an integer is in the valid range.
fn assert_integer_in_range<I>(v: I) -> Result<(), serde_json::Error>
where
i64: TryFrom<I>,
{
let res = i64::try_from(v);
match res {
Ok(MIN_VALID_INTEGER..=MAX_VALID_INTEGER) => Ok(()),
Ok(_) | Err(_) => Err(serde_json::Error::custom("integer out of range")),
}
}
/// A JSON formatter that ensures all strings are encoded as per the [Canonical
/// JSON](https://matrix.org/docs/spec/appendices#canonical-json) spec.
pub struct CanonicalFormatter;
impl Formatter for CanonicalFormatter {
fn write_string_fragment<W>(&mut self, writer: &mut W, fragment: &str) -> io::Result<()>
where
W: ?Sized + io::Write,
{
// `fragment` only contains characters that are not escaped, and don't
// need to be escaped, so they can be written directly to the writer.
writer.write_all(fragment.as_bytes())
}
fn write_char_escape<W>(
&mut self,
writer: &mut W,
char_escape: serde_json::ser::CharEscape,
) -> io::Result<()>
where
W: ?Sized + io::Write,
{
use serde_json::ser::CharEscape::*;
let s = match char_escape {
Quote => b"\\\"" as &[u8],
ReverseSolidus => b"\\\\",
Solidus => b"/", // Note: this doesn't need to be escaped (and appears unused in serde_json).
Backspace => b"\\b",
FormFeed => b"\\f",
LineFeed => b"\\n",
CarriageReturn => b"\\r",
Tab => b"\\t",
AsciiControl(byte) => {
static HEX_DIGITS: [u8; 16] = *b"0123456789abcdef";
let bytes = &[
b'\\',
b'u',
b'0',
b'0',
HEX_DIGITS[(byte >> 4) as usize],
HEX_DIGITS[(byte & 0xF) as usize],
];
return writer.write_all(bytes);
}
};
writer.write_all(s)
}
}
/// A JSON serializer that outputs [Canonical
/// JSON](https://matrix.org/docs/spec/appendices#canonical-json).
pub struct CanonicalSerializer<W> {
inner: Serializer<W, CanonicalFormatter>,
options: CanonicalizationOptions,
}
impl<W> CanonicalSerializer<W>
where
W: Write,
{
/// Create a new serializer that writes the canonical JSON bytes to the
/// given writer.
pub fn new(writer: W, options: CanonicalizationOptions) -> Self {
Self {
inner: Serializer::with_formatter(writer, CanonicalFormatter),
options,
}
}
}
// We implement the serializer by proxying all calls to the standard
// `serde_json` serializer, except where we a) buffer up maps and structs so that we can
// sort them, and b) ensure that all numbers are integers in the valid range.
impl<'a, W> serde::Serializer for &'a mut CanonicalSerializer<W>
where
W: Write,
{
type Ok = <&'a mut Serializer<W, CanonicalFormatter> as serde::Serializer>::Ok;
type Error = <&'a mut Serializer<W, CanonicalFormatter> as serde::Serializer>::Error;
type SerializeSeq =
<&'a mut Serializer<W, CanonicalFormatter> as serde::Serializer>::SerializeSeq;
type SerializeTuple =
<&'a mut Serializer<W, CanonicalFormatter> as serde::Serializer>::SerializeTuple;
type SerializeTupleStruct =
<&'a mut Serializer<W, CanonicalFormatter> as serde::Serializer>::SerializeTupleStruct;
type SerializeTupleVariant =
<&'a mut Serializer<W, CanonicalFormatter> as serde::Serializer>::SerializeTupleVariant;
type SerializeMap = CanonicalSerializeMap<'a, W>;
type SerializeStruct = CanonicalSerializeStruct<'a, W>;
type SerializeStructVariant =
<&'a mut Serializer<W, CanonicalFormatter> as serde::Serializer>::SerializeStructVariant;
fn serialize_bool(self, v: bool) -> Result<Self::Ok, Self::Error> {
self.inner.serialize_bool(v)
}
fn serialize_i8(self, v: i8) -> Result<Self::Ok, Self::Error> {
assert_integer_in_range(v)?;
self.inner.serialize_i8(v)
}
fn serialize_i16(self, v: i16) -> Result<Self::Ok, Self::Error> {
assert_integer_in_range(v)?;
self.inner.serialize_i16(v)
}
fn serialize_i32(self, v: i32) -> Result<Self::Ok, Self::Error> {
assert_integer_in_range(v)?;
self.inner.serialize_i32(v)
}
fn serialize_i64(self, v: i64) -> Result<Self::Ok, Self::Error> {
if self.options.enforce_int_range {
assert_integer_in_range(v)?;
}
self.inner.serialize_i64(v)
}
fn serialize_i128(self, v: i128) -> Result<Self::Ok, Self::Error> {
if self.options.enforce_int_range {
assert_integer_in_range(v)?;
}
self.inner.serialize_i128(v)
}
fn serialize_u8(self, v: u8) -> Result<Self::Ok, Self::Error> {
assert_integer_in_range(v)?;
self.inner.serialize_u8(v)
}
fn serialize_u16(self, v: u16) -> Result<Self::Ok, Self::Error> {
self.inner.serialize_u16(v)
}
fn serialize_u32(self, v: u32) -> Result<Self::Ok, Self::Error> {
assert_integer_in_range(v)?;
self.inner.serialize_u32(v)
}
fn serialize_u64(self, v: u64) -> Result<Self::Ok, Self::Error> {
if self.options.enforce_int_range {
assert_integer_in_range(v)?;
}
self.inner.serialize_u64(v)
}
fn serialize_u128(self, v: u128) -> Result<Self::Ok, Self::Error> {
if self.options.enforce_int_range {
assert_integer_in_range(v)?;
}
self.inner.serialize_u128(v)
}
fn serialize_f32(self, _: f32) -> Result<Self::Ok, Self::Error> {
Err(serde_json::Error::custom(
"non-integer numbers are not allowed",
))
}
fn serialize_f64(self, _: f64) -> Result<Self::Ok, Self::Error> {
Err(serde_json::Error::custom(
"non-integer numbers are not allowed",
))
}
fn serialize_char(self, v: char) -> Result<Self::Ok, Self::Error> {
self.inner.serialize_char(v)
}
fn serialize_str(self, v: &str) -> Result<Self::Ok, Self::Error> {
self.inner.serialize_str(v)
}
fn serialize_bytes(self, v: &[u8]) -> Result<Self::Ok, Self::Error> {
self.inner.serialize_bytes(v)
}
fn serialize_none(self) -> Result<Self::Ok, Self::Error> {
self.inner.serialize_none()
}
fn serialize_some<T>(self, value: &T) -> Result<Self::Ok, Self::Error>
where
T: serde::Serialize + ?Sized,
{
self.inner.serialize_some(value)
}
fn serialize_unit(self) -> Result<Self::Ok, Self::Error> {
self.inner.serialize_unit()
}
fn serialize_unit_struct(self, name: &'static str) -> Result<Self::Ok, Self::Error> {
self.inner.serialize_unit_struct(name)
}
fn serialize_unit_variant(
self,
name: &'static str,
variant_index: u32,
variant: &'static str,
) -> Result<Self::Ok, Self::Error> {
self.inner
.serialize_unit_variant(name, variant_index, variant)
}
fn serialize_newtype_struct<T>(
self,
name: &'static str,
value: &T,
) -> Result<Self::Ok, Self::Error>
where
T: serde::Serialize + ?Sized,
{
self.inner.serialize_newtype_struct(name, value)
}
fn serialize_newtype_variant<T>(
self,
name: &'static str,
variant_index: u32,
variant: &'static str,
value: &T,
) -> Result<Self::Ok, Self::Error>
where
T: serde::Serialize + ?Sized,
{
self.inner
.serialize_newtype_variant(name, variant_index, variant, value)
}
fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {
self.inner.serialize_seq(len)
}
fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple, Self::Error> {
self.inner.serialize_tuple(len)
}
fn serialize_tuple_struct(
self,
name: &'static str,
len: usize,
) -> Result<Self::SerializeTupleStruct, Self::Error> {
self.inner.serialize_tuple_struct(name, len)
}
fn serialize_tuple_variant(
self,
name: &'static str,
variant_index: u32,
variant: &'static str,
len: usize,
) -> Result<Self::SerializeTupleVariant, Self::Error> {
self.inner
.serialize_tuple_variant(name, variant_index, variant, len)
}
fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap, Self::Error> {
Ok(CanonicalSerializeMap::new(
&mut self.inner,
self.options.clone(),
))
}
fn serialize_struct(
self,
name: &'static str,
len: usize,
) -> Result<Self::SerializeStruct, Self::Error> {
// We want to disallow `RawValue` as we don't know if its contents is
// canonical JSON.
//
// Note: the `name` here comes from `serde_json::raw::TOKEN`, which
// unfortunately isn't exported by the crate.
if name == "$serde_json::private::RawValue" {
return Err(Self::Error::custom("`RawValue` is not supported"));
}
CanonicalSerializeStruct::new(name, len, &mut self.inner, self.options.clone())
}
fn serialize_struct_variant(
self,
name: &'static str,
variant_index: u32,
variant: &'static str,
len: usize,
) -> Result<Self::SerializeStructVariant, Self::Error> {
self.inner
.serialize_struct_variant(name, variant_index, variant, len)
}
fn collect_str<T>(self, value: &T) -> Result<Self::Ok, Self::Error>
where
T: std::fmt::Display + ?Sized,
{
self.inner.collect_str(value)
}
}
/// A helper type for [`CanonicalSerializer`] that serializes JSON maps in
/// lexicographic order.
#[doc(hidden)]
pub struct CanonicalSerializeMap<'a, W> {
// We buffer up the key and serialized value for each field we see.
// The BTreeMap will then serialize in lexicographic order.
map: BTreeMap<String, Box<RawValue>>,
// A key which we're still waiting for a value for
last_key: Option<String>,
// The serializer to use to write the sorted map too.
ser: &'a mut Serializer<W, CanonicalFormatter>,
options: CanonicalizationOptions,
}
impl<'a, W> CanonicalSerializeMap<'a, W> {
fn new(
ser: &'a mut Serializer<W, CanonicalFormatter>,
options: CanonicalizationOptions,
) -> Self {
Self {
map: BTreeMap::new(),
last_key: None,
ser,
options,
}
}
}
impl<'a, W> SerializeMap for CanonicalSerializeMap<'a, W>
where
W: Write,
{
type Ok = ();
type Error = serde_json::Error;
fn serialize_key<T>(&mut self, key: &T) -> Result<(), Self::Error>
where
T: serde::Serialize + ?Sized,
{
if self.last_key.is_some() {
// This can only happen if `serialize_key` is called multiple times
// in a row without a `serialize_value` call in between. This
// violates the contract of `SerializeMap`.
return Err(Self::Error::custom(
"serialize_key called multiple times in a row without serialize_value",
));
}
// Parse the `key` into a string.
let key_string = if let Value::String(str) = serde_json::to_value(key)? {
str
} else {
return Err(Self::Error::custom("key must be a string"));
};
self.last_key = Some(key_string);
Ok(())
}
fn serialize_value<T>(&mut self, value: &T) -> Result<(), Self::Error>
where
T: serde::Serialize + ?Sized,
{
let key_string = if let Some(key_string) = self.last_key.take() {
key_string
} else {
// `serde` should ensure that for every `serialize_key` there is a
// `serialize_field` call, so `last_key` should never be None here.
unreachable!()
};
// We serialize the value canonically, then store it as a `RawValue` in
// the buffer map.
let value_string = to_string_canonical(value, self.options.clone())?;
self.map
.insert(key_string, RawValue::from_string(value_string)?);
Ok(())
}
fn end(self) -> Result<Self::Ok, Self::Error> {
// No more entries in the map being serialized, so we can now serialize
// our buffered map (which will be serialized in the correct order as
// its a BTreeMap).
self.map.serialize(self.ser)?;
Ok(())
}
}
/// A helper type for [`CanonicalSerializer`] that serializes structs in
/// lexicographic order.
#[doc(hidden)]
pub struct CanonicalSerializeStruct<'a, W: Write> {
name: &'static str,
// We buffer up the key and serialized value for each field we see.
// The BTreeMap will then serialize in lexicographic order.
map: BTreeMap<&'static str, Box<RawValue>>,
options: CanonicalizationOptions,
// The serializer to use to write the sorted map too.
struct_serializer:
<&'a mut Serializer<W, CanonicalFormatter> as serde::Serializer>::SerializeStruct,
}
impl<'a, W> CanonicalSerializeStruct<'a, W>
where
W: Write,
{
fn new(
name: &'static str,
len: usize,
ser: &'a mut Serializer<W, CanonicalFormatter>,
options: CanonicalizationOptions,
) -> Result<Self, serde_json::Error> {
let struct_serializer = ser.serialize_struct(name, len)?;
Ok(Self {
name,
map: BTreeMap::new(),
options,
struct_serializer,
})
}
}
impl<'a, W> SerializeStruct for CanonicalSerializeStruct<'a, W>
where
W: Write,
{
type Ok = ();
type Error = serde_json::Error;
fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<(), Self::Error>
where
T: Serialize + ?Sized,
{
// Check if this is the special case of `SERDE_JSON_NUMBER_TOKEN`,
// which is used when serializing numbers with the `arbitrary_precision`
// feature. If so, we can just serialize it directly without
// canonicalizing it first, as `serde_json` will have already serialized
// it in a canonical way.
if key == SERDE_JSON_NUMBER_TOKEN && self.name == SERDE_JSON_NUMBER_TOKEN {
if self.options.enforce_int_range {
// We need to check that the number is in the valid range, as
// `serde_json` won't have done this for us as we're using the
// `arbitrary_precision` feature.
// The value here will be something that serializes to a JSON
// string containing the number, so we first serialize it to a
// Value and pull the string out, then parse it as a `Number`.
let serde_val = serde_json::to_value(value)?;
let serde_json::Value::String(number_str) = serde_val else {
return Err(serde_json::Error::custom("invalid number"));
};
let number: Number = number_str
.parse()
.map_err(|_| serde_json::Error::custom("invalid number"))?;
// Now check that the number is an integer in the valid range.
if let Some(int) = number.as_i64() {
assert_integer_in_range(int)?;
} else {
// Can't be cast to an i64, so it must be out of range.
return Err(serde_json::Error::custom("integer out of range"));
}
}
self.struct_serializer.serialize_field(key, value)?;
return Ok(());
}
// We serialize the value canonically, then store it as a `RawValue` in
// the buffer map.
let value_string = to_string_canonical(value, self.options.clone())?;
self.map.insert(key, RawValue::from_string(value_string)?);
Ok(())
}
fn end(mut self) -> Result<Self::Ok, Self::Error> {
if self.name == SERDE_JSON_NUMBER_TOKEN {
// Map must be empty in this case, as `SERDE_JSON_NUMBER_TOKEN`
// only has one field and we've handled it in `serialize_field`.
if !self.map.is_empty() {
return Err(Self::Error::custom(format!(
"unexpected fields in `{}`",
SERDE_JSON_NUMBER_TOKEN
)));
}
}
for (key, value) in self.map {
self.struct_serializer.serialize_field(key, &value)?;
}
SerializeStruct::end(self.struct_serializer)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use itertools::Itertools;
use serde::Serializer;
use serde_json::json;
use super::*;
#[test]
fn empty() {
let test = json!({});
let json_string = to_string_canonical(&test, CanonicalizationOptions::strict()).unwrap();
assert_eq!(json_string, r#"{}"#);
}
#[test]
fn order_struct_fields() {
#[derive(Serialize)]
struct Test {
b: u8,
a: u8,
}
let test = Test { b: 1, a: 2 };
let json_string = to_string_canonical(&test, CanonicalizationOptions::strict()).unwrap();
assert_eq!(json_string, r#"{"a":2,"b":1}"#);
}
#[test]
fn strings() {
let test = json!({
"a": "\u{1F37B}",
"b": "\n",
"c": "\x01",
});
let json_string = to_string_canonical(&test, CanonicalizationOptions::strict()).unwrap();
assert_eq!(json_string, r#"{"a":"🍻","b":"\n","c":"\u0001"}"#);
}
#[test]
fn escapes() {
let mut buffer;
let mut char_buffer = [0u8; 4];
// Ensure that we encode every UTF-8 character correctly
for c in '\0'..='\u{10FFFF}' {
// Serialize the character and strip out the quotes to make comparison easier.
let json_string = to_string_canonical(&c, CanonicalizationOptions::strict()).unwrap();
let unquoted_json_string = &json_string[1..json_string.len() - 1];
let expected = match c {
// Some control characters have specific escape codes.
'\x08' => r"\b",
'\x09' => r"\t",
'\x0A' => r"\n",
'\x0C' => r"\f",
'\x0D' => r"\r",
'\x22' => r#"\""#,
'\x5C' => r"\\",
// Otherwise any character less than \x1F gets escaped as
// `\u00xx`
'\0'..='\x1F' => {
buffer = format!(r"\u00{:02x}", c as u32);
&buffer
}
// And everything else doesn't get escaped
_ => c.encode_utf8(&mut char_buffer),
};
// The serialized character will be wrapped in quotes.
assert_eq!(unquoted_json_string, expected);
}
}
#[test]
fn nested_map() {
let test = json!({
"a": {"b": 1}
});
let json_string = to_string_canonical(&test, CanonicalizationOptions::strict()).unwrap();
assert_eq!(json_string, r#"{"a":{"b":1}}"#);
}
#[test]
fn floats() {
assert!(to_string_canonical(&100.0f32, CanonicalizationOptions::strict()).is_err());
assert!(to_string_canonical(&100.0f64, CanonicalizationOptions::strict()).is_err());
}
#[test]
fn integers() {
assert_eq!(
to_string_canonical(&100u8, CanonicalizationOptions::strict()).unwrap(),
"100"
);
assert_eq!(
to_string_canonical(&100u16, CanonicalizationOptions::strict()).unwrap(),
"100"
);
assert_eq!(
to_string_canonical(&100u32, CanonicalizationOptions::strict()).unwrap(),
"100"
);
assert_eq!(
to_string_canonical(&100u64, CanonicalizationOptions::strict()).unwrap(),
"100"
);
assert_eq!(
to_string_canonical(&100u128, CanonicalizationOptions::strict()).unwrap(),
"100"
);
assert_eq!(
to_string_canonical(&100i8, CanonicalizationOptions::strict()).unwrap(),
"100"
);
assert_eq!(
to_string_canonical(&100i16, CanonicalizationOptions::strict()).unwrap(),
"100"
);
assert_eq!(
to_string_canonical(&100i32, CanonicalizationOptions::strict()).unwrap(),
"100"
);
assert_eq!(
to_string_canonical(&100i64, CanonicalizationOptions::strict()).unwrap(),
"100"
);
assert_eq!(
to_string_canonical(&100i128, CanonicalizationOptions::strict()).unwrap(),
"100"
);
assert!(to_string_canonical(&2u64.pow(60), CanonicalizationOptions::strict()).is_err());
assert!(to_string_canonical(&2u128.pow(60), CanonicalizationOptions::strict()).is_err());
assert!(to_string_canonical(&2i64.pow(60), CanonicalizationOptions::strict()).is_err());
assert!(to_string_canonical(&2i128.pow(60), CanonicalizationOptions::strict()).is_err());
assert!(to_string_canonical(&-(2i64.pow(60)), CanonicalizationOptions::strict()).is_err());
assert!(to_string_canonical(&-(2i128.pow(60)), CanonicalizationOptions::strict()).is_err());
}
#[test]
fn bigints() {
// Create a `serde_json::Number` that is too big to be represented as an
// i64, but can be represented as a string.
let bigint_string = "10000000000000000000000000000000000000";
let value: serde_json::Number = bigint_string.parse().unwrap();
// This should work with relaxed option.
assert_eq!(
to_string_canonical(&value, CanonicalizationOptions::relaxed()).unwrap(),
bigint_string
);
// But should fail with strict option, as it's out of range.
assert!(to_string_canonical(&value, CanonicalizationOptions::strict()).is_err());
}
#[test]
fn backwards_compatibility() {
assert_eq!(
to_string_canonical(&u64::MAX, CanonicalizationOptions::relaxed()).unwrap(),
format!("{}", u64::MAX)
);
assert_eq!(
to_string_canonical(&u128::MAX, CanonicalizationOptions::relaxed()).unwrap(),
format!("{}", u128::MAX)
);
assert_eq!(
to_string_canonical(&i128::MAX, CanonicalizationOptions::relaxed()).unwrap(),
format!("{}", i128::MAX)
);
assert_eq!(
to_string_canonical(&-i128::MAX, CanonicalizationOptions::relaxed()).unwrap(),
format!("{}", -i128::MAX)
);
}
#[test]
fn hashmap_order() {
let mut test = HashMap::new();
test.insert("e", 1);
test.insert("d", 1);
test.insert("c", 1);
test.insert("b", 1);
test.insert("a", 1);
test.insert("AA", 1);
let json_string = to_string_canonical(&test, CanonicalizationOptions::strict()).unwrap();
assert_eq!(json_string, r#"{"AA":1,"a":1,"b":1,"c":1,"d":1,"e":1}"#);
}
#[test]
fn raw_value() {
let raw_value = RawValue::from_string("{}".to_string()).unwrap();
assert!(to_string_canonical(&raw_value, CanonicalizationOptions::strict()).is_err());
}
#[test]
fn map_with_duplicate_keys() {
let mut output = Vec::new();
let mut serializer =
CanonicalSerializer::new(&mut output, CanonicalizationOptions::strict());
let mut map_serializer = serializer.serialize_map(None).unwrap();
map_serializer.serialize_entry("a", &1).unwrap();
map_serializer.serialize_entry("a", &2).unwrap();
// Also try with different representations of the same key (e.g. `\t` and `\u{0009}`).
map_serializer.serialize_entry("\t", &2).unwrap();
map_serializer.serialize_entry("\u{0009}", &2).unwrap();
SerializeMap::end(map_serializer).unwrap();
assert_eq!(String::from_utf8(output).unwrap(), r#"{"\t":2,"a":2}"#);
}
#[test]
fn map_with_out_of_order_keys() {
let mut output = Vec::new();
let mut serializer =
CanonicalSerializer::new(&mut output, CanonicalizationOptions::strict());
let mut map_serializer = serializer.serialize_map(None).unwrap();
// An ordered list of keys to insert, and the expected way they should be serialized.
let ascii_order = [
('\0', r"\u0000"),
('\t', r"\t"),
(' ', r" "),
('!', r"!"),
('"', r#"\""#),
('&', r"&"),
('A', r"A"),
('\\', r"\\"),
('a', r"a"),
('🍻', r"🍻"),
];
// Double check that the keys are in the expected order.
assert!(ascii_order.is_sorted_by_key(|(c, _)| u32::from(*c)));
// Serialize the keys in the reverse order.
for (c, _) in ascii_order.iter().rev() {
map_serializer.serialize_entry(c, &1).unwrap();
}
SerializeMap::end(map_serializer).unwrap();
// The expected JSON should have the keys in the correct order, and the
// correct escaping.
let expected_json_inner = ascii_order
.iter()
.map(|(_, escaped)| format!(r#""{escaped}":1"#))
.join(",");
let expected_json = r"{".to_owned() + &expected_json_inner + r"}";
assert_eq!(String::from_utf8(output).unwrap(), expected_json);
}
}