make use of esc string opt

This commit is contained in:
Conrad Ludgate
2025-07-17 12:21:24 +01:00
parent d6f4dc4949
commit ce6bbca8d7
6 changed files with 66 additions and 47 deletions

View File

@@ -19,7 +19,7 @@ struct Bar {
pub fn escape(c: &mut Criterion) {
c.bench_function("small", |b| bench_json_encode_inner(b, "object_key"));
c.bench_function("small_static", |b| {
bench_json_encode_inner(b, &json::EscapedStr::from_static("object_key"));
bench_json_encode_inner(b, json::esc!("object_key"));
});
c.bench_function("large_fmt", |b| {
let value = Foo {

View File

@@ -278,14 +278,14 @@ impl<'buf> ListSer<'buf> {
#[cfg(test)]
mod tests {
use crate::{Null, ValueSer};
use crate::{Null, ValueSer, esc};
#[test]
fn object() {
let mut buf = vec![];
let mut object = ValueSer::new(&mut buf).object();
object.entry("foo", "bar");
object.entry("baz", Null);
object.entry(esc!("foo"), "bar");
object.entry(esc!("baz"), Null);
object.finish();
assert_eq!(buf, br#"{"foo":"bar","baz":null}"#);
@@ -306,8 +306,8 @@ mod tests {
fn object_macro() {
let res = crate::value_to_string!(|obj| {
crate::value_as_object!(|obj| {
obj.entry("foo", "bar");
obj.entry("baz", Null);
obj.entry(esc!("foo"), "bar");
obj.entry(esc!("baz"), Null);
})
});
@@ -363,7 +363,7 @@ mod tests {
let entry = obj.key("2");
let entry = {
let mut nested_obj = entry.object();
nested_obj.entry("foo", "bar");
nested_obj.entry(esc!("foo"), "bar");
nested_obj.rollback()
};

View File

@@ -84,3 +84,11 @@ macro_rules! value_as_list {
res
}};
}
/// A helper macro that ensures the provided string literal does not need escaping.
#[macro_export]
macro_rules! esc {
($str:literal) => {
const { $crate::EscapedStr::from_static($str) }
};
}

View File

@@ -8,14 +8,12 @@
//!
//! With modifications by Conrad Ludgate on behalf of Databricks.
use std::{
borrow::Cow,
fmt::{self, Write},
};
use std::fmt::{self, Write};
use crate::{KeyEncoder, ValueEncoder, ValueSer};
pub struct EscapedStr(Cow<'static, [u8]>);
#[repr(transparent)]
pub struct EscapedStr([u8]);
impl EscapedStr {
/// Assumes the string does not need escaping.
@@ -23,31 +21,37 @@ impl EscapedStr {
/// # Panics
///
/// This will panic if the string does need escaping.
pub const fn from_static(s: &'static str) -> Self {
#[inline(always)]
pub const fn from_static(s: &'static str) -> &'static Self {
let bytes = s.as_bytes();
let mut i = 0;
while i < bytes.len() {
let byte = bytes[i];
let escape = ESCAPE[byte as usize];
assert!(escape == 0, "a character in the string needed escaping");
if byte < 0x20 || byte == b'"' || byte == b'\\' {
panic!("the string needs escaping");
}
i += 1;
}
Self(Cow::Borrowed(bytes))
// safety: this EscapedStr is transparent over [u8].
unsafe { std::mem::transmute::<&[u8], &EscapedStr>(bytes) }
}
/// Escapes the string eagerly.
pub fn escape(s: &str) -> Self {
pub fn escape(s: &str) -> Box<Self> {
let mut writer = Vec::with_capacity(s.len());
Collect { buf: &mut writer }
.write_str(s)
.expect("formatting should not error");
Self(Cow::Owned(writer))
let bytes = writer.into_boxed_slice();
// safety: this EscapedStr is transparent over [u8].
unsafe { std::mem::transmute::<Box<[u8]>, Box<EscapedStr>>(bytes) }
}
}