mod compound;
mod encoders;
mod key;
#[cfg(test)]
mod tests;
mod text;
use serde::{Serialize, ser};
use std::{cmp::Ordering, fmt, io, str};
use text::{U64_TEXT_CAPACITY, format_u64, write_escaped};
#[derive(Debug)]
pub(crate) enum CanonicalError {
Io(io::Error),
NonFiniteNumber,
InvalidKey,
Message(Box<str>),
}
impl fmt::Display for CanonicalError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Io(error) => write!(formatter, "{error}"),
Self::NonFiniteNumber => formatter.write_str("invalid float value"),
Self::InvalidKey => formatter.write_str("invalid UTF-8 key"),
Self::Message(message) => formatter.write_str(message),
}
}
}
impl std::error::Error for CanonicalError {}
impl ser::Error for CanonicalError {
fn custom<T: fmt::Display>(message: T) -> Self {
Self::Message(message.to_string().into_boxed_str())
}
}
pub(crate) fn compare_utf16_order(left: &str, right: &str) -> Ordering {
compare_utf16_bytes(left.as_bytes(), right.as_bytes())
}
fn compare_utf16_bytes(left: &[u8], right: &[u8]) -> Ordering {
let shared = left.len().min(right.len());
let mut index = 0;
while index < shared && left[index] == right[index] {
index += 1;
}
if index == shared {
return left.len().cmp(&right.len());
}
let (left_byte, right_byte) = (left[index], right[index]);
if left_byte < 0x80 && right_byte < 0x80 {
return left_byte.cmp(&right_byte);
}
let left = str::from_utf8(left).expect("canonical keys are valid UTF-8");
let right = str::from_utf8(right).expect("canonical keys are valid UTF-8");
left.encode_utf16().cmp(right.encode_utf16())
}
const MAX_INTEGRAL_FAST_PATH: f64 = 9_007_199_254_740_992.0;
#[derive(Clone, Copy)]
struct Frame {
members_start: usize,
buf_start: usize,
}
#[derive(Clone, Copy)]
struct Member {
key_start: usize,
key_end: usize,
value_end: usize,
}
pub(crate) struct CanonicalSerializer<W> {
out: W,
buf: Vec<u8>,
frames: Vec<Frame>,
members: Vec<Member>,
scratch: Vec<u8>,
sorted: Vec<Member>,
}
impl<W: io::Write> CanonicalSerializer<W> {
pub(crate) const fn new(out: W) -> Self {
Self {
out,
buf: Vec::new(),
frames: Vec::new(),
members: Vec::new(),
scratch: Vec::new(),
sorted: Vec::new(),
}
}
pub(crate) fn write_value<T>(&mut self, value: &T) -> Result<(), CanonicalError>
where
T: ?Sized + Serialize,
{
value.serialize(&mut *self)
}
pub(crate) fn write_string(&mut self, value: &str) -> Result<(), CanonicalError> {
self.emit_string(value)
}
pub(crate) fn write_punctuation(&mut self, bytes: &[u8]) -> Result<(), CanonicalError> {
self.emit(bytes)
}
fn emit(&mut self, bytes: &[u8]) -> Result<(), CanonicalError> {
if self.frames.is_empty() {
self.out.write_all(bytes).map_err(CanonicalError::Io)
} else {
self.buf.extend_from_slice(bytes);
Ok(())
}
}
fn emit_string(&mut self, value: &str) -> Result<(), CanonicalError> {
if self.frames.is_empty() {
write_escaped(&mut self.out, value).map_err(CanonicalError::Io)
} else {
write_escaped(&mut self.buf, value).map_err(CanonicalError::Io)
}
}
fn emit_u64(&mut self, value: u64) -> Result<(), CanonicalError> {
let mut buffer = [0_u8; U64_TEXT_CAPACITY];
let text = format_u64(value, &mut buffer);
if self.frames.is_empty() {
self.out.write_all(text).map_err(CanonicalError::Io)
} else {
self.buf.extend_from_slice(text);
Ok(())
}
}
fn emit_i64(&mut self, value: i64) -> Result<(), CanonicalError> {
if value < 0 {
self.emit(b"-")?;
}
self.emit_u64(value.unsigned_abs())
}
fn emit_f64(&mut self, value: f64) -> Result<(), CanonicalError> {
if !value.is_finite() {
return Err(CanonicalError::NonFiniteNumber);
}
if value == 0.0 {
return self.emit(b"0");
}
if value.fract() == 0.0 && value.abs() <= MAX_INTEGRAL_FAST_PATH {
#[expect(
clippy::cast_possible_truncation,
reason = "the magnitude check keeps the value within i64"
)]
return self.emit_i64(value as i64);
}
let mut buffer = ryu_js::Buffer::new();
let text = buffer.format_finite(value);
self.emit(text.as_bytes())
}
fn begin_object(&mut self) {
self.frames.push(Frame {
members_start: self.members.len(),
buf_start: self.buf.len(),
});
}
fn end_object(&mut self) -> Result<(), CanonicalError> {
let frame = self.frames.pop().expect("end_object without begin_object");
self.sorted.clear();
self.sorted
.extend(self.members.drain(frame.members_start..));
let buf = &self.buf;
self.sorted.sort_by(|left, right| {
compare_utf16_bytes(
&buf[left.key_start..left.key_end],
&buf[right.key_start..right.key_end],
)
});
render_object(&self.buf, &self.sorted, &mut self.scratch)?;
self.buf.truncate(frame.buf_start);
if self.frames.is_empty() {
self.out
.write_all(&self.scratch)
.map_err(CanonicalError::Io)
} else {
self.buf.extend_from_slice(&self.scratch);
Ok(())
}
}
fn open_static_key(&mut self, key: &str) -> (usize, usize) {
let key_start = self.buf.len();
self.buf.extend_from_slice(key.as_bytes());
(key_start, self.buf.len())
}
fn push_member(&mut self, key_start: usize, key_end: usize) {
self.members.push(Member {
key_start,
key_end,
value_end: self.buf.len(),
});
}
}
fn render_object(
buf: &[u8],
sorted: &[Member],
scratch: &mut Vec<u8>,
) -> Result<(), CanonicalError> {
scratch.clear();
scratch.push(b'{');
for (index, member) in sorted.iter().enumerate() {
let duplicated_later = sorted.get(index + 1).is_some_and(|next| {
buf[next.key_start..next.key_end] == buf[member.key_start..member.key_end]
});
if duplicated_later {
continue;
}
if scratch.len() > 1 {
scratch.push(b',');
}
let key =
str::from_utf8(&buf[member.key_start..member.key_end]).expect("keys are UTF-8 text");
write_escaped(scratch, key).map_err(CanonicalError::Io)?;
scratch.push(b':');
scratch.extend_from_slice(&buf[member.key_end..member.value_end]);
}
scratch.push(b'}');
Ok(())
}