diff --git a/libs/proxy/json/benches/escape.rs b/libs/proxy/json/benches/escape.rs index 17dfe6b4c5..43571b52bd 100644 --- a/libs/proxy/json/benches/escape.rs +++ b/libs/proxy/json/benches/escape.rs @@ -18,6 +18,9 @@ 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")); + }); c.bench_function("large_fmt", |b| { let value = Foo { some_field: Bar { diff --git a/libs/proxy/json/src/lib.rs b/libs/proxy/json/src/lib.rs index 2d9750b5c0..b4b2cf1a68 100644 --- a/libs/proxy/json/src/lib.rs +++ b/libs/proxy/json/src/lib.rs @@ -81,6 +81,7 @@ mod macros; mod str; mod value; +pub use str::EscapedStr; pub use value::{KeyEncoder, Null, ValueEncoder}; #[must_use] diff --git a/libs/proxy/json/src/str.rs b/libs/proxy/json/src/str.rs index 8ebf9e0aad..67fb2950b1 100644 --- a/libs/proxy/json/src/str.rs +++ b/libs/proxy/json/src/str.rs @@ -8,10 +8,63 @@ //! //! With modifications by Conrad Ludgate on behalf of Databricks. -use std::fmt::{self, Write}; +use std::{ + borrow::Cow, + fmt::{self, Write}, +}; use crate::{KeyEncoder, ValueEncoder, ValueSer}; +pub struct EscapedStr(Cow<'static, [u8]>); + +impl EscapedStr { + /// Assumes the string does not need escaping. + /// + /// # Panics + /// + /// This will panic if the string does need escaping. + pub const fn from_static(s: &'static str) -> 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"); + + i += 1; + } + + Self(Cow::Borrowed(bytes)) + } + + /// Escapes the string eagerly. + pub fn escape(s: &str) -> 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)) + } +} + +impl KeyEncoder for &EscapedStr {} +impl ValueEncoder for &EscapedStr { + fn encode(self, v: crate::ValueSer<'_>) { + let buf = &mut *v.buf; + buf.reserve(2 + self.0.len()); + + buf.push(b'"'); + buf.extend_from_slice(&self.0); + buf.push(b'"'); + + v.finish(); + } +} + impl KeyEncoder for &str {} impl ValueEncoder for &str { #[inline] diff --git a/libs/proxy/json/src/value.rs b/libs/proxy/json/src/value.rs index 2ed21d956f..168d3ef548 100644 --- a/libs/proxy/json/src/value.rs +++ b/libs/proxy/json/src/value.rs @@ -84,6 +84,7 @@ impl ValueEncoder for Option { } /// Represents the JSON null value. +#[derive(Clone, Copy)] pub struct Null; impl ValueEncoder for Null {